@dbx-tools/shared-core 0.6.82 → 0.6.86

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/src/context.ts ADDED
@@ -0,0 +1,164 @@
1
+ /**
2
+ * The ambient CONTEXT a cached value belongs to, and a cache keyed by it.
3
+ *
4
+ * Most of the expensive lookups in this repo are pure functions of the directory
5
+ * (or the page) they were asked from: `npm prefix`, `git rev-parse`, a `.env`
6
+ * file, `databricks bundle validate`. Memoizing them with
7
+ * {@link functionModule.memoize} is wrong in exactly one situation, and it is the
8
+ * situation a CLI hits: the process changes directory, and the cached answer now
9
+ * describes somewhere else. Every caller therefore grew the same guard - resolve
10
+ * `cwd`, compare it against `process.cwd()`, and skip the cache when they differ
11
+ * (see the `cacheEnabled` dance this module replaced in node-core's `project`).
12
+ *
13
+ * This is that guard, once. A cache slot stores the context it was loaded under,
14
+ * so a context change (a moved `cwd`, a different origin) MISSES rather than
15
+ * returns a stale value, and a lookup for some OTHER directory is not cached at
16
+ * all - one off-context call must not evict or poison the hot path.
17
+ *
18
+ * The context is the process working directory on a server and
19
+ * `location.origin` in a browser, whichever this runtime has. Browser-safe:
20
+ * `process` and `location` are reached through `globalThis` and guarded, so this
21
+ * module needs no Node types and simply has no context off-process.
22
+ *
23
+ * @example
24
+ * const roots = context.cached(["project", "npm", "prefix"], (cwd) =>
25
+ * spawnSync("npm", ["prefix"], { cwd }),
26
+ * );
27
+ *
28
+ * @module
29
+ */
30
+
31
+ import type { OneOrMany } from "./object.ts";
32
+
33
+ /** A loader for a cached value; receives the resolved context. */
34
+ export type ContextLoader<T> = (context: string | undefined) => T;
35
+
36
+ interface CacheEntry {
37
+ context: string;
38
+ value: unknown;
39
+ }
40
+
41
+ const CACHE = new Map<string, CacheEntry>();
42
+
43
+ /**
44
+ * The current context: the process working directory, else `location.origin`,
45
+ * else `undefined` (a runtime with neither, where nothing is cacheable).
46
+ */
47
+ export function getContext(): string | undefined {
48
+ for (const context of getContexts()) return context;
49
+ return undefined;
50
+ }
51
+
52
+ /**
53
+ * Whether `value` names a context this runtime is CURRENTLY in - the live `cwd`
54
+ * or origin. This is the cacheability test: a value loaded for some other
55
+ * directory is correct but not shareable, so it is never stored.
56
+ */
57
+ export function isContext(value: unknown): boolean {
58
+ const context = toContext(value);
59
+ if (context === undefined) return false;
60
+ for (const candidate of getContexts()) {
61
+ if (candidate === context) return true;
62
+ }
63
+ return false;
64
+ }
65
+
66
+ /**
67
+ * Load `name` through `loader`, reusing the cached value while the context is
68
+ * unchanged.
69
+ *
70
+ * `context` selects what the value is loaded FOR: omitted, `null`, or `"."` mean
71
+ * the current context. It is passed straight to `loader`, so a loader never has
72
+ * to reach for `process.cwd()` itself.
73
+ *
74
+ * Caching happens only when the resolved context is one this runtime is in
75
+ * ({@link isContext}), so `cached(name, loader, "/somewhere/else")` always calls
76
+ * `loader` and stores nothing. A stored entry remembers its context and is
77
+ * discarded on the next call once that context changes. A thrown error is never
78
+ * cached; a rejected promise evicts its entry so a later call retries.
79
+ *
80
+ * `name` identifies the slot and must be stable across calls - pass a
81
+ * module-qualified list of string parts (`["project", command, ...args]`) so two
82
+ * modules cannot collide on one slot.
83
+ */
84
+ export function cached<T>(
85
+ name: OneOrMany<string>,
86
+ loader: ContextLoader<T>,
87
+ context?: string | null,
88
+ ): T {
89
+ const resolved = toContext(context) ?? getContext();
90
+ const key = resolved !== undefined && isContext(resolved) ? cacheKey(name) : undefined;
91
+ if (key !== undefined) {
92
+ const entry = CACHE.get(key);
93
+ if (entry && entry.context === resolved) return entry.value as T;
94
+ }
95
+ const value = loader(resolved);
96
+ if (key !== undefined && resolved !== undefined) {
97
+ const entry: CacheEntry = { context: resolved, value };
98
+ CACHE.set(key, entry);
99
+ if (isThenable(value)) {
100
+ void Promise.resolve(value).catch(() => {
101
+ if (CACHE.get(key) === entry) CACHE.delete(key);
102
+ });
103
+ }
104
+ }
105
+ return value;
106
+ }
107
+
108
+ /** Drop one cached slot, or the whole cache when `name` is omitted. */
109
+ export function clear(name?: OneOrMany<string>): void {
110
+ if (name === undefined) CACHE.clear();
111
+ else CACHE.delete(cacheKey(name));
112
+ }
113
+
114
+ /**
115
+ * Canonical slot key. `JSON.stringify` over the parts rather than a hash: the
116
+ * parts are already strings, and an exact key cannot collide two unrelated
117
+ * lookups onto one slot the way a truncated digest can.
118
+ */
119
+ function cacheKey(name: OneOrMany<string>): string {
120
+ return JSON.stringify(typeof name === "string" ? [name] : name);
121
+ }
122
+
123
+ /**
124
+ * Every context this runtime reports, in precedence order. A server yields its
125
+ * `cwd`; a browser yields `location.origin`. A hybrid (a worker with both) yields
126
+ * both, which is what makes {@link isContext} accept either.
127
+ */
128
+ function* getContexts(): Generator<string, void, void> {
129
+ const global = globalThis as {
130
+ process?: { cwd?: () => string };
131
+ location?: { origin?: string };
132
+ };
133
+ const cwd = global.process?.cwd;
134
+ if (typeof cwd === "function") {
135
+ let current: unknown;
136
+ try {
137
+ current = cwd();
138
+ } catch {
139
+ current = undefined;
140
+ }
141
+ const context = toContext(current);
142
+ if (context !== undefined) yield context;
143
+ }
144
+ const origin = toContext(global.location?.origin);
145
+ if (origin !== undefined) yield origin;
146
+ }
147
+
148
+ /** A non-blank string, with `"."` treated as "the current context" (unresolved). */
149
+ function toContext(value: unknown): string | undefined {
150
+ if (typeof value !== "string") return undefined;
151
+ const trimmed = value.trim();
152
+ return trimmed && trimmed !== "." ? trimmed : undefined;
153
+ }
154
+
155
+ /** Duck-type any value with a callable `.then` as a thenable. */
156
+ function isThenable(value: unknown): value is PromiseLike<unknown> {
157
+ return (
158
+ value instanceof Promise ||
159
+ (typeof value === "object" &&
160
+ value !== null &&
161
+ "then" in value &&
162
+ typeof (value as PromiseLike<unknown>).then === "function")
163
+ );
164
+ }
package/src/object.ts CHANGED
@@ -26,7 +26,8 @@
26
26
  /** Lazy sequence over iterable source(s). See {@link sequence}. */
27
27
  export type Sequence<T> = SequenceImpl<T>;
28
28
 
29
- type SequenceSource<T> = Iterable<T> | ReadonlyMap<unknown, T> | OneOrMany<T> | null | undefined;
29
+ type SequenceSource<T> =
30
+ Iterable<T> | ReadonlyMap<unknown, T> | OneOrMany<T> | Extract<T, string> | null | undefined;
30
31
 
31
32
  /**
32
33
  * A non-scalar {@link Iterable} - one to treat as a collection of elements
@@ -159,7 +160,7 @@ function sequenceSources<T>(...sources: SequenceSource<T>[]): Iterable<T>[] {
159
160
  const sourceIterables: Iterable<T>[] = [];
160
161
  for (const source of sources) {
161
162
  if (source == null || (isCollection(source) && isEmpty(source))) continue;
162
- sourceIterables.push(values(source));
163
+ sourceIterables.push(isContainer<T>(source) ? values(source) : [source as T]);
163
164
  }
164
165
  return sourceIterables;
165
166
  }
@@ -979,7 +980,7 @@ export function toStableKey(value: unknown, seen: Set<object> = new Set()): stri
979
980
  *
980
981
  * @example
981
982
  * return {
982
- * ...optional("appId", env.text("MICROSOFT_APP_ID")),
983
+ * ...optional("appId", configuredAppId),
983
984
  * ...optional("endpoint", config.endpoint),
984
985
  * };
985
986
  */
package/lib/src/env.d.ts DELETED
@@ -1,101 +0,0 @@
1
- /**
2
- * Reading configuration out of the environment.
3
- *
4
- * Every plugin config in this repo resolves the same way: take the typed
5
- * config value when the caller set one, else fall back to one or more
6
- * environment variables, else a default. Written by hand that becomes a
7
- * `config.x ?? Number(process.env.X)` chain per field, and each package grew
8
- * its own `fromEnv` / `resolvePositiveInt` helper with slightly different
9
- * coercion rules. These are those helpers, once.
10
- *
11
- * Browser-safe: `process` is reached through `globalThis` and guarded, so this
12
- * module needs no Node types and is inert in a browser (every lookup misses and
13
- * the caller's fallback applies).
14
- *
15
- * @module
16
- */
17
- /** Highest valid TCP port number. */
18
- export declare const MAX_TCP_PORT = 65535;
19
- /** One env var name, or several tried in order. */
20
- export type EnvKey = string | readonly string[];
21
- /**
22
- * Detect the Databricks App runtime from its required environment shape.
23
- *
24
- * A valid app has a non-empty name, an HTTP(S) workspace host, and a valid
25
- * `DATABRICKS_APP_PORT`. Reads the ambient environment when none is supplied.
26
- */
27
- export declare function isAppEnv(source?: Record<string, string | undefined>): boolean;
28
- /**
29
- * The PRIMARY (current, non-deprecated) name in an {@link EnvKey}.
30
- *
31
- * Use this when naming a variable in a log line or error rather than indexing
32
- * `keys[0]`: an {@link EnvKey} may be a bare string, and `"TUNNEL_X"[0]` is the
33
- * character `"T"`, which produces a message naming a variable that does not
34
- * exist. Returns `""` only for an empty list, which no caller should have.
35
- *
36
- * @example
37
- * logger.warn(`${env.name(JWT_SECRET_ENV)} is not set`);
38
- */
39
- export declare function name(keys: EnvKey): string;
40
- /**
41
- * First non-empty value among `keys`, trimmed, else `null`.
42
- *
43
- * Several names for one setting is the norm (a package-specific variable plus
44
- * the Databricks-standard one), so `keys` is order-sensitive: earlier names win.
45
- *
46
- * @example
47
- * env.text(["TEAMS_APP_ID", "MICROSOFT_APP_ID"]);
48
- */
49
- export declare function text(keys: EnvKey): string | null;
50
- /**
51
- * Resolve a string setting: `configured` when set and non-empty, else the first
52
- * non-empty variable among `keys`, else `null`.
53
- *
54
- * @example
55
- * env.string(config.host, "SMTP_HOST");
56
- */
57
- export declare function string(configured: unknown, keys: EnvKey): string | null;
58
- /**
59
- * Resolve a boolean setting through {@link toBoolean}, so the loose spellings an
60
- * env var actually carries (`1`, `on`, `yes`, ...) are accepted. Returns
61
- * `undefined` when neither source is interpretable, letting the caller pick a
62
- * default with `??`.
63
- *
64
- * @example
65
- * env.boolean(config.fuzzy, "WEB_SEARCH_FUZZY") ?? true;
66
- */
67
- export declare function boolean(configured: unknown, keys: EnvKey): boolean | undefined;
68
- /**
69
- * Resolve a positive-number setting that may be fractional (a score threshold,
70
- * a ratio): `configured` when it is a finite number greater than zero, else the
71
- * first variable among `keys` that parses that way, else `fallback`.
72
- *
73
- * {@link positiveInt} is the right choice for a count; this one keeps the
74
- * fraction, so a `0.4` threshold does not floor to `0`.
75
- *
76
- * @example
77
- * env.positiveNumber(config.fuzzyThreshold, "SEARCH_FUZZY_THRESHOLD", 0.4);
78
- */
79
- export declare function positiveNumber(configured: unknown, keys: EnvKey, fallback: number): number;
80
- /**
81
- * Resolve a positive-integer setting (a port, timeout, page size, or cap):
82
- * `configured` when it is a finite number greater than zero, else the first
83
- * variable among `keys` that parses that way, else `fallback`. Floored, so a
84
- * fractional value can't leak into a count.
85
- *
86
- * A non-numeric or non-positive value is treated as absent rather than fatal -
87
- * these are ceilings and timeouts where a sane default beats a boot failure.
88
- *
89
- * @example
90
- * env.positiveInt(config.timeoutMs, "SEARCH_TIMEOUT_MS", 30_000);
91
- */
92
- export declare function positiveInt(configured: unknown, keys: EnvKey, fallback: number): number;
93
- /**
94
- * Resolve a list setting through {@link parseList}, so an array from typed
95
- * config and a `"a, b c"` env string normalize identically. Returns `[]` when
96
- * neither source has entries.
97
- *
98
- * @example
99
- * env.list(config.modelFallbacks, "WEB_SEARCH_MODEL_FALLBACKS");
100
- */
101
- export declare function list(configured: string | readonly string[] | undefined | null, keys: EnvKey, transform?: (entry: string) => string): string[];
package/lib/src/env.js DELETED
@@ -1,153 +0,0 @@
1
- /**
2
- * Reading configuration out of the environment.
3
- *
4
- * Every plugin config in this repo resolves the same way: take the typed
5
- * config value when the caller set one, else fall back to one or more
6
- * environment variables, else a default. Written by hand that becomes a
7
- * `config.x ?? Number(process.env.X)` chain per field, and each package grew
8
- * its own `fromEnv` / `resolvePositiveInt` helper with slightly different
9
- * coercion rules. These are those helpers, once.
10
- *
11
- * Browser-safe: `process` is reached through `globalThis` and guarded, so this
12
- * module needs no Node types and is inert in a browser (every lookup misses and
13
- * the caller's fallback applies).
14
- *
15
- * @module
16
- */
17
- import { toBoolean, toNumber } from "./object.js";
18
- import { parseList, trimToNull } from "./string.js";
19
- /** Highest valid TCP port number. */
20
- export const MAX_TCP_PORT = 65_535;
21
- /** The ambient environment, or `{}` off-process (a browser). */
22
- function environment() {
23
- return globalThis.process?.env ?? {};
24
- }
25
- /**
26
- * Detect the Databricks App runtime from its required environment shape.
27
- *
28
- * A valid app has a non-empty name, an HTTP(S) workspace host, and a valid
29
- * `DATABRICKS_APP_PORT`. Reads the ambient environment when none is supplied.
30
- */
31
- export function isAppEnv(source = environment()) {
32
- const appName = source.DATABRICKS_APP_NAME?.trim();
33
- const host = source.DATABRICKS_HOST?.trim();
34
- const port = source.DATABRICKS_APP_PORT?.trim();
35
- if (!appName || !host || !port)
36
- return false;
37
- try {
38
- if (!["http:", "https:"].includes(new URL(host).protocol))
39
- return false;
40
- }
41
- catch {
42
- return false;
43
- }
44
- const portNumber = toNumber(port);
45
- return (portNumber !== undefined &&
46
- Number.isInteger(portNumber) &&
47
- portNumber >= 1 &&
48
- portNumber <= MAX_TCP_PORT);
49
- }
50
- /**
51
- * The PRIMARY (current, non-deprecated) name in an {@link EnvKey}.
52
- *
53
- * Use this when naming a variable in a log line or error rather than indexing
54
- * `keys[0]`: an {@link EnvKey} may be a bare string, and `"TUNNEL_X"[0]` is the
55
- * character `"T"`, which produces a message naming a variable that does not
56
- * exist. Returns `""` only for an empty list, which no caller should have.
57
- *
58
- * @example
59
- * logger.warn(`${env.name(JWT_SECRET_ENV)} is not set`);
60
- */
61
- export function name(keys) {
62
- return typeof keys === "string" ? keys : (keys[0] ?? "");
63
- }
64
- /**
65
- * First non-empty value among `keys`, trimmed, else `null`.
66
- *
67
- * Several names for one setting is the norm (a package-specific variable plus
68
- * the Databricks-standard one), so `keys` is order-sensitive: earlier names win.
69
- *
70
- * @example
71
- * env.text(["TEAMS_APP_ID", "MICROSOFT_APP_ID"]);
72
- */
73
- export function text(keys) {
74
- const env = environment();
75
- for (const key of typeof keys === "string" ? [keys] : keys) {
76
- const value = trimToNull(env[key]);
77
- if (value !== null)
78
- return value;
79
- }
80
- return null;
81
- }
82
- /**
83
- * Resolve a string setting: `configured` when set and non-empty, else the first
84
- * non-empty variable among `keys`, else `null`.
85
- *
86
- * @example
87
- * env.string(config.host, "SMTP_HOST");
88
- */
89
- export function string(configured, keys) {
90
- return trimToNull(configured) ?? text(keys);
91
- }
92
- /**
93
- * Resolve a boolean setting through {@link toBoolean}, so the loose spellings an
94
- * env var actually carries (`1`, `on`, `yes`, ...) are accepted. Returns
95
- * `undefined` when neither source is interpretable, letting the caller pick a
96
- * default with `??`.
97
- *
98
- * @example
99
- * env.boolean(config.fuzzy, "WEB_SEARCH_FUZZY") ?? true;
100
- */
101
- export function boolean(configured, keys) {
102
- return toBoolean(configured) ?? toBoolean(text(keys));
103
- }
104
- /**
105
- * Resolve a positive-number setting that may be fractional (a score threshold,
106
- * a ratio): `configured` when it is a finite number greater than zero, else the
107
- * first variable among `keys` that parses that way, else `fallback`.
108
- *
109
- * {@link positiveInt} is the right choice for a count; this one keeps the
110
- * fraction, so a `0.4` threshold does not floor to `0`.
111
- *
112
- * @example
113
- * env.positiveNumber(config.fuzzyThreshold, "SEARCH_FUZZY_THRESHOLD", 0.4);
114
- */
115
- export function positiveNumber(configured, keys, fallback) {
116
- return toPositiveNumber(configured) ?? toPositiveNumber(text(keys)) ?? fallback;
117
- }
118
- /**
119
- * Resolve a positive-integer setting (a port, timeout, page size, or cap):
120
- * `configured` when it is a finite number greater than zero, else the first
121
- * variable among `keys` that parses that way, else `fallback`. Floored, so a
122
- * fractional value can't leak into a count.
123
- *
124
- * A non-numeric or non-positive value is treated as absent rather than fatal -
125
- * these are ceilings and timeouts where a sane default beats a boot failure.
126
- *
127
- * @example
128
- * env.positiveInt(config.timeoutMs, "SEARCH_TIMEOUT_MS", 30_000);
129
- */
130
- export function positiveInt(configured, keys, fallback) {
131
- return toPositiveInt(configured) ?? toPositiveInt(text(keys)) ?? fallback;
132
- }
133
- function toPositiveNumber(value) {
134
- const parsed = toNumber(value);
135
- return parsed !== undefined && parsed > 0 ? parsed : undefined;
136
- }
137
- function toPositiveInt(value) {
138
- const parsed = toPositiveNumber(value);
139
- return parsed === undefined ? undefined : Math.floor(parsed);
140
- }
141
- /**
142
- * Resolve a list setting through {@link parseList}, so an array from typed
143
- * config and a `"a, b c"` env string normalize identically. Returns `[]` when
144
- * neither source has entries.
145
- *
146
- * @example
147
- * env.list(config.modelFallbacks, "WEB_SEARCH_MODEL_FALLBACKS");
148
- */
149
- export function list(configured, keys, transform) {
150
- const fromConfig = parseList(configured, transform);
151
- return fromConfig.length > 0 ? fromConfig : parseList(text(keys), transform);
152
- }
153
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZW52LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2Vudi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7O0dBZUc7QUFFSCxPQUFPLEVBQUUsU0FBUyxFQUFFLFFBQVEsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUNsRCxPQUFPLEVBQUUsU0FBUyxFQUFFLFVBQVUsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQU9wRCxxQ0FBcUM7QUFDckMsTUFBTSxDQUFDLE1BQU0sWUFBWSxHQUFHLE1BQU0sQ0FBQztBQUtuQyxnRUFBZ0U7QUFDaEUsU0FBUyxXQUFXO0lBQ2xCLE9BQVEsVUFBd0MsQ0FBQyxPQUFPLEVBQUUsR0FBRyxJQUFJLEVBQUUsQ0FBQztBQUN0RSxDQUFDO0FBRUQ7Ozs7O0dBS0c7QUFDSCxNQUFNLFVBQVUsUUFBUSxDQUFDLFNBQTZDLFdBQVcsRUFBRTtJQUNqRixNQUFNLE9BQU8sR0FBRyxNQUFNLENBQUMsbUJBQW1CLEVBQUUsSUFBSSxFQUFFLENBQUM7SUFDbkQsTUFBTSxJQUFJLEdBQUcsTUFBTSxDQUFDLGVBQWUsRUFBRSxJQUFJLEVBQUUsQ0FBQztJQUM1QyxNQUFNLElBQUksR0FBRyxNQUFNLENBQUMsbUJBQW1CLEVBQUUsSUFBSSxFQUFFLENBQUM7SUFDaEQsSUFBSSxDQUFDLE9BQU8sSUFBSSxDQUFDLElBQUksSUFBSSxDQUFDLElBQUk7UUFBRSxPQUFPLEtBQUssQ0FBQztJQUU3QyxJQUFJLENBQUM7UUFDSCxJQUFJLENBQUMsQ0FBQyxPQUFPLEVBQUUsUUFBUSxDQUFDLENBQUMsUUFBUSxDQUFDLElBQUksR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDLFFBQVEsQ0FBQztZQUFFLE9BQU8sS0FBSyxDQUFDO0lBQzFFLENBQUM7SUFBQyxNQUFNLENBQUM7UUFDUCxPQUFPLEtBQUssQ0FBQztJQUNmLENBQUM7SUFFRCxNQUFNLFVBQVUsR0FBRyxRQUFRLENBQUMsSUFBSSxDQUFDLENBQUM7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QUMsU0FBUyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsRUFBRSxTQUFTLENBQUMsQ0FBQztBQUMvRSxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBSZWFkaW5nIGNvbmZpZ3VyYXRpb24gb3V0IG9mIHRoZSBlbnZpcm9ubWVudC5cbiAqXG4gKiBFdmVyeSBwbHVnaW4gY29uZmlnIGluIHRoaXMgcmVwbyByZXNvbHZlcyB0aGUgc2FtZSB3YXk6IHRha2UgdGhlIHR5cGVkXG4gKiBjb25maWcgdmFsdWUgd2hlbiB0aGUgY2FsbGVyIHNldCBvbmUsIGVsc2UgZmFsbCBiYWNrIHRvIG9uZSBvciBtb3JlXG4gKiBlbnZpcm9ubWVudCB2YXJpYWJsZXMsIGVsc2UgYSBkZWZhdWx0LiBXcml0dGVuIGJ5IGhhbmQgdGhhdCBiZWNvbWVzIGFcbiAqIGBjb25maWcueCA/PyBOdW1iZXIocHJvY2Vzcy5lbnYuWClgIGNoYWluIHBlciBmaWVsZCwgYW5kIGVhY2ggcGFja2FnZSBncmV3XG4gKiBpdHMgb3duIGBmcm9tRW52YCAvIGByZXNvbHZlUG9zaXRpdmVJbnRgIGhlbHBlciB3aXRoIHNsaWdodGx5IGRpZmZlcmVudFxuICogY29lcmNpb24gcnVsZXMuIFRoZXNlIGFyZSB0aG9zZSBoZWxwZXJzLCBvbmNlLlxuICpcbiAqIEJyb3dzZXItc2FmZTogYHByb2Nlc3NgIGlzIHJlYWNoZWQgdGhyb3VnaCBgZ2xvYmFsVGhpc2AgYW5kIGd1YXJkZWQsIHNvIHRoaXNcbiAqIG1vZHVsZSBuZWVkcyBubyBOb2RlIHR5cGVzIGFuZCBpcyBpbmVydCBpbiBhIGJyb3dzZXIgKGV2ZXJ5IGxvb2t1cCBtaXNzZXMgYW5kXG4gKiB0aGUgY2FsbGVyJ3MgZmFsbGJhY2sgYXBwbGllcykuXG4gKlxuICogQG1vZHVsZVxuICovXG5cbmltcG9ydCB7IHRvQm9vbGVhbiwgdG9OdW1iZXIgfSBmcm9tIFwiLi9vYmplY3QudHNcIjtcbmltcG9ydCB7IHBhcnNlTGlzdCwgdHJpbVRvTnVsbCB9IGZyb20gXCIuL3N0cmluZy50c1wiO1xuXG4vKiogYHByb2Nlc3NgLXNoYXBlZCB2aWV3IG9mZiBgZ2xvYmFsVGhpc2AsIHNvIG5vIG5vZGUgdHlwZXMgYXJlIG5lZWRlZC4gKi9cbmludGVyZmFjZSBQcm9jZXNzTGlrZSB7XG4gIGVudj86IFJlY29yZDxzdHJpbmcsIHN0cmluZyB8IHVuZGVmaW5lZD47XG59XG5cbi8qKiBIaWdoZXN0IHZhbGlkIFRDUCBwb3J0IG51bWJlci4gKi9cbmV4cG9ydCBjb25zdCBNQVhfVENQX1BPUlQgPSA2NV81MzU7XG5cbi8qKiBPbmUgZW52IHZhciBuYW1lLCBvciBzZXZlcmFsIHRyaWVkIGluIG9yZGVyLiAqL1xuZXhwb3J0IHR5cGUgRW52S2V5ID0gc3RyaW5nIHwgcmVhZG9ubHkgc3RyaW5nW107XG5cbi8qKiBUaGUgYW1iaWVudCBlbnZpcm9ubWVudCwgb3IgYHt9YCBvZmYtcHJvY2VzcyAoYSBicm93c2VyKS4gKi9cbmZ1bmN0aW9uIGVudmlyb25tZW50KCk6IFJlY29yZDxzdHJpbmcsIHN0cmluZyB8IHVuZGVmaW5lZD4ge1xuICByZXR1cm4gKGdsb2JhbFRoaXMgYXMgeyBwcm9jZXNzPzogUHJvY2Vzc0xpa2UgfSkucHJvY2Vzcz8uZW52ID8/IHt9O1xufVxuXG4vKipcbiAqIERldGVjdCB0aGUgRGF0YWJyaWNrcyBBcHAgcnVudGltZSBmcm9tIGl0cyByZXF1aXJlZCBlbnZpcm9ubWVudCBzaGFwZS5cbiAqXG4gKiBBIHZhbGlkIGFwcCBoYXMgYSBub24tZW1wdHkgbmFtZSwgYW4gSFRUUChTKSB3b3Jrc3BhY2UgaG9zdCwgYW5kIGEgdmFsaWRcbiAqIGBEQVRBQlJJQ0tTX0FQUF9QT1JUYC4gUmVhZHMgdGhlIGFtYmllbnQgZW52aXJvbm1lbnQgd2hlbiBub25lIGlzIHN1cHBsaWVkLlxuICovXG5leHBvcnQgZnVuY3Rpb24gaXNBcHBFbnYoc291cmNlOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmcgfCB1bmRlZmluZWQ+ID0gZW52aXJvbm1lbnQoKSk6IGJvb2xlYW4ge1xuICBjb25zdCBhcHBOYW1lID0gc291cmNlLkRBVEFCUklDS1NfQVBQX05BTUU/LnRyaW0oKTtcbiAgY29uc3QgaG9zdCA9IHNvdXJjZS5EQVRBQlJJQ0tTX0hPU1Q/LnRyaW0oKTtcbiAgY29uc3QgcG9ydCA9IHNvdXJjZS5EQVRBQlJJQ0tTX0FQUF9QT1JUPy50cmltKCk7XG4gIGlmICghYXBwTmFtZSB8fCAhaG9zdCB8fCAhcG9ydCkgcmV0dXJuIGZhbHNlO1xuXG4gIHRyeSB7XG4gICAgaWYgKCFbXCJodHRwOlwiLCBcImh0dHBzOlwiXS5pbmNsdWRlcyhuZXcgVVJMKGhvc3QpLnByb3RvY29sKSkgcmV0dXJuIGZhbHNlO1xuICB9IGNhdGNoIHtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cblxuICBjb25zdCBwb3J0TnVtYmVyID0gdG9OdW1iZXIocG9ydCk7XG4gIHJldHVybiAoXG4gICAgcG9ydE51bWJlciAhPT0gdW5kZWZpbmVkICYmXG4gICAgTnVtYmVyLmlzSW50ZWdlcihwb3J0TnVtYmVyKSAmJlxuICAgIHBvcnROdW1iZXIgPj0gMSAmJlxuICAgIHBvcnROdW1iZXIgPD0gTUFYX1RDUF9QT1JUXG4gICk7XG59XG5cbi8qKlxuICogVGhlIFBSSU1BUlkgKGN1cnJlbnQsIG5vbi1kZXByZWNhdGVkKSBuYW1lIGluIGFuIHtAbGluayBFbnZLZXl9LlxuICpcbiAqIFVzZSB0aGlzIHdoZW4gbmFtaW5nIGEgdmFyaWFibGUgaW4gYSBsb2cgbGluZSBvciBlcnJvciByYXRoZXIgdGhhbiBpbmRleGluZ1xuICogYGtleXNbMF1gOiBhbiB7QGxpbmsgRW52S2V5fSBtYXkgYmUgYSBiYXJlIHN0cmluZywgYW5kIGBcIlRVTk5FTF9YXCJbMF1gIGlzIHRoZVxuICogY2hhcmFjdGVyIGBcIlRcImAsIHdoaWNoIHByb2R1Y2VzIGEgbWVzc2FnZSBuYW1pbmcgYSB2YXJpYWJsZSB0aGF0IGRvZXMgbm90XG4gKiBleGlzdC4gUmV0dXJucyBgXCJcImAgb25seSBmb3IgYW4gZW1wdHkgbGlzdCwgd2hpY2ggbm8gY2FsbGVyIHNob3VsZCBoYXZlLlxuICpcbiAqIEBleGFtcGxlXG4gKiBsb2dnZXIud2FybihgJHtlbnYubmFtZShKV1RfU0VDUkVUX0VOVil9IGlzIG5vdCBzZXRgKTtcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIG5hbWUoa2V5czogRW52S2V5KTogc3RyaW5nIHtcbiAgcmV0dXJuIHR5cGVvZiBrZXlzID09PSBcInN0cmluZ1wiID8ga2V5cyA6IChrZXlzWzBdID8/IFwiXCIpO1xufVxuXG4vKipcbiAqIEZpcnN0IG5vbi1lbXB0eSB2YWx1ZSBhbW9uZyBga2V5c2AsIHRyaW1tZWQsIGVsc2UgYG51bGxgLlxuICpcbiAqIFNldmVyYWwgbmFtZXMgZm9yIG9uZSBzZXR0aW5nIGlzIHRoZSBub3JtIChhIHBhY2thZ2Utc3BlY2lmaWMgdmFyaWFibGUgcGx1c1xuICogdGhlIERhdGFicmlja3Mtc3RhbmRhcmQgb25lKSwgc28gYGtleXNgIGlzIG9yZGVyLXNlbnNpdGl2ZTogZWFybGllciBuYW1lcyB3aW4uXG4gKlxuICogQGV4YW1wbGVcbiAqIGVudi50ZXh0KFtcIlRFQU1TX0FQUF9JRFwiLCBcIk1JQ1JPU09GVF9BUFBfSURcIl0pO1xuICovXG5leHBvcnQgZnVuY3Rpb24gdGV4dChrZXlzOiBFbnZLZXkpOiBzdHJpbmcgfCBudWxsIHtcbiAgY29uc3QgZW52ID0gZW52aXJvbm1lbnQoKTtcbiAgZm9yIChjb25zdCBrZXkgb2YgdHlwZW9mIGtleXMgPT09IFwic3RyaW5nXCIgPyBba2V5c10gOiBrZXlzKSB7XG4gICAgY29uc3QgdmFsdWUgPSB0cmltVG9OdWxsKGVudltrZXldKTtcbiAgICBpZiAodmFsdWUgIT09IG51bGwpIHJldHVybiB2YWx1ZTtcbiAgfVxuICByZXR1cm4gbnVsbDtcbn1cblxuLyoqXG4gKiBSZXNvbHZlIGEgc3RyaW5nIHNldHRpbmc6IGBjb25maWd1cmVkYCB3aGVuIHNldCBhbmQgbm9uLWVtcHR5LCBlbHNlIHRoZSBmaXJzdFxuICogbm9uLWVtcHR5IHZhcmlhYmxlIGFtb25nIGBrZXlzYCwgZWxzZSBgbnVsbGAuXG4gKlxuICogQGV4YW1wbGVcbiAqIGVudi5zdHJpbmcoY29uZmlnLmhvc3QsIFwiU01UUF9IT1NUXCIpO1xuICovXG5leHBvcnQgZnVuY3Rpb24gc3RyaW5nKGNvbmZpZ3VyZWQ6IHVua25vd24sIGtleXM6IEVudktleSk6IHN0cmluZyB8IG51bGwge1xuICByZXR1cm4gdHJpbVRvTnVsbChjb25maWd1cmVkKSA/PyB0ZXh0KGtleXMpO1xufVxuXG4vKipcbiAqIFJlc29sdmUgYSBib29sZWFuIHNldHRpbmcgdGhyb3VnaCB7QGxpbmsgdG9Cb29sZWFufSwgc28gdGhlIGxvb3NlIHNwZWxsaW5ncyBhblxuICogZW52IHZhciBhY3R1YWxseSBjYXJyaWVzIChgMWAsIGBvbmAsIGB5ZXNgLCAuLi4pIGFyZSBhY2NlcHRlZC4gUmV0dXJuc1xuICogYHVuZGVmaW5lZGAgd2hlbiBuZWl0aGVyIHNvdXJjZSBpcyBpbnRlcnByZXRhYmxlLCBsZXR0aW5nIHRoZSBjYWxsZXIgcGljayBhXG4gKiBkZWZhdWx0IHdpdGggYD8/YC5cbiAqXG4gKiBAZXhhbXBsZVxuICogZW52LmJvb2xlYW4oY29uZmlnLmZ1enp5LCBcIldFQl9TRUFSQ0hfRlVaWllcIikgPz8gdHJ1ZTtcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGJvb2xlYW4oY29uZmlndXJlZDogdW5rbm93biwga2V5czogRW52S2V5KTogYm9vbGVhbiB8IHVuZGVmaW5lZCB7XG4gIHJldHVybiB0b0Jvb2xlYW4oY29uZmlndXJlZCkgPz8gdG9Cb29sZWFuKHRleHQoa2V5cykpO1xufVxuXG4vKipcbiAqIFJlc29sdmUgYSBwb3NpdGl2ZS1udW1iZXIgc2V0dGluZyB0aGF0IG1heSBiZSBmcmFjdGlvbmFsIChhIHNjb3JlIHRocmVzaG9sZCxcbiAqIGEgcmF0aW8pOiBgY29uZmlndXJlZGAgd2hlbiBpdCBpcyBhIGZpbml0ZSBudW1iZXIgZ3JlYXRlciB0aGFuIHplcm8sIGVsc2UgdGhlXG4gKiBmaXJzdCB2YXJpYWJsZSBhbW9uZyBga2V5c2AgdGhhdCBwYXJzZXMgdGhhdCB3YXksIGVsc2UgYGZhbGxiYWNrYC5cbiAqXG4gKiB7QGxpbmsgcG9zaXRpdmVJbnR9IGlzIHRoZSByaWdodCBjaG9pY2UgZm9yIGEgY291bnQ7IHRoaXMgb25lIGtlZXBzIHRoZVxuICogZnJhY3Rpb24sIHNvIGEgYDAuNGAgdGhyZXNob2xkIGRvZXMgbm90IGZsb29yIHRvIGAwYC5cbiAqXG4gKiBAZXhhbXBsZVxuICogZW52LnBvc2l0aXZlTnVtYmVyKGNvbmZpZy5mdXp6eVRocmVzaG9sZCwgXCJTRUFSQ0hfRlVaWllfVEhSRVNIT0xEXCIsIDAuNCk7XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBwb3NpdGl2ZU51bWJlcihjb25maWd1cmVkOiB1bmtub3duLCBrZXlzOiBFbnZLZXksIGZhbGxiYWNrOiBudW1iZXIpOiBudW1iZXIge1xuICByZXR1cm4gdG9Qb3NpdGl2ZU51bWJlcihjb25maWd1cmVkKSA/PyB0b1Bvc2l0aXZlTnVtYmVyKHRleHQoa2V5cykpID8/IGZhbGxiYWNrO1xufVxuXG4vKipcbiAqIFJlc29sdmUgYSBwb3NpdGl2ZS1pbnRlZ2VyIHNldHRpbmcgKGEgcG9ydCwgdGltZW91dCwgcGFnZSBzaXplLCBvciBjYXApOlxuICogYGNvbmZpZ3VyZWRgIHdoZW4gaXQgaXMgYSBmaW5pdGUgbnVtYmVyIGdyZWF0ZXIgdGhhbiB6ZXJvLCBlbHNlIHRoZSBmaXJzdFxuICogdmFyaWFibGUgYW1vbmcgYGtleXNgIHRoYXQgcGFyc2VzIHRoYXQgd2F5LCBlbHNlIGBmYWxsYmFja2AuIEZsb29yZWQsIHNvIGFcbiAqIGZyYWN0aW9uYWwgdmFsdWUgY2FuJ3QgbGVhayBpbnRvIGEgY291bnQuXG4gKlxuICogQSBub24tbnVtZXJpYyBvciBub24tcG9zaXRpdmUgdmFsdWUgaXMgdHJlYXRlZCBhcyBhYnNlbnQgcmF0aGVyIHRoYW4gZmF0YWwgLVxuICogdGhlc2UgYXJlIGNlaWxpbmdzIGFuZCB0aW1lb3V0cyB3aGVyZSBhIHNhbmUgZGVmYXVsdCBiZWF0cyBhIGJvb3QgZmFpbHVyZS5cbiAqXG4gKiBAZXhhbXBsZVxuICogZW52LnBvc2l0aXZlSW50KGNvbmZpZy50aW1lb3V0TXMsIFwiU0VBUkNIX1RJTUVPVVRfTVNcIiwgMzBfMDAwKTtcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHBvc2l0aXZlSW50KGNvbmZpZ3VyZWQ6IHVua25vd24sIGtleXM6IEVudktleSwgZmFsbGJhY2s6IG51bWJlcik6IG51bWJlciB7XG4gIHJldHVybiB0b1Bvc2l0aXZlSW50KGNvbmZpZ3VyZWQpID8/IHRvUG9zaXRpdmVJbnQodGV4dChrZXlzKSkgPz8gZmFsbGJhY2s7XG59XG5cbmZ1bmN0aW9uIHRvUG9zaXRpdmVOdW1iZXIodmFsdWU6IHVua25vd24pOiBudW1iZXIgfCB1bmRlZmluZWQge1xuICBjb25zdCBwYXJzZWQgPSB0b051bWJlcih2YWx1ZSk7XG4gIHJldHVybiBwYXJzZWQgIT09IHVuZGVmaW5lZCAmJiBwYXJzZWQgPiAwID8gcGFyc2VkIDogdW5kZWZpbmVkO1xufVxuXG5mdW5jdGlvbiB0b1Bvc2l0aXZlSW50KHZhbHVlOiB1bmtub3duKTogbnVtYmVyIHwgdW5kZWZpbmVkIHtcbiAgY29uc3QgcGFyc2VkID0gdG9Qb3NpdGl2ZU51bWJlcih2YWx1ZSk7XG4gIHJldHVybiBwYXJzZWQgPT09IHVuZGVmaW5lZCA/IHVuZGVmaW5lZCA6IE1hdGguZmxvb3IocGFyc2VkKTtcbn1cblxuLyoqXG4gKiBSZXNvbHZlIGEgbGlzdCBzZXR0aW5nIHRocm91Z2gge0BsaW5rIHBhcnNlTGlzdH0sIHNvIGFuIGFycmF5IGZyb20gdHlwZWRcbiAqIGNvbmZpZyBhbmQgYSBgXCJhLCBiIGNcImAgZW52IHN0cmluZyBub3JtYWxpemUgaWRlbnRpY2FsbHkuIFJldHVybnMgYFtdYCB3aGVuXG4gKiBuZWl0aGVyIHNvdXJjZSBoYXMgZW50cmllcy5cbiAqXG4gKiBAZXhhbXBsZVxuICogZW52Lmxpc3QoY29uZmlnLm1vZGVsRmFsbGJhY2tzLCBcIldFQl9TRUFSQ0hfTU9ERUxfRkFMTEJBQ0tTXCIpO1xuICovXG5leHBvcnQgZnVuY3Rpb24gbGlzdChcbiAgY29uZmlndXJlZDogc3RyaW5nIHwgcmVhZG9ubHkgc3RyaW5nW10gfCB1bmRlZmluZWQgfCBudWxsLFxuICBrZXlzOiBFbnZLZXksXG4gIHRyYW5zZm9ybT86IChlbnRyeTogc3RyaW5nKSA9PiBzdHJpbmcsXG4pOiBzdHJpbmdbXSB7XG4gIGNvbnN0IGZyb21Db25maWcgPSBwYXJzZUxpc3QoY29uZmlndXJlZCwgdHJhbnNmb3JtKTtcbiAgcmV0dXJuIGZyb21Db25maWcubGVuZ3RoID4gMCA/IGZyb21Db25maWcgOiBwYXJzZUxpc3QodGV4dChrZXlzKSwgdHJhbnNmb3JtKTtcbn1cbiJdfQ==
package/src/env.ts DELETED
@@ -1,177 +0,0 @@
1
- /**
2
- * Reading configuration out of the environment.
3
- *
4
- * Every plugin config in this repo resolves the same way: take the typed
5
- * config value when the caller set one, else fall back to one or more
6
- * environment variables, else a default. Written by hand that becomes a
7
- * `config.x ?? Number(process.env.X)` chain per field, and each package grew
8
- * its own `fromEnv` / `resolvePositiveInt` helper with slightly different
9
- * coercion rules. These are those helpers, once.
10
- *
11
- * Browser-safe: `process` is reached through `globalThis` and guarded, so this
12
- * module needs no Node types and is inert in a browser (every lookup misses and
13
- * the caller's fallback applies).
14
- *
15
- * @module
16
- */
17
-
18
- import { toBoolean, toNumber } from "./object.ts";
19
- import { parseList, trimToNull } from "./string.ts";
20
-
21
- /** `process`-shaped view off `globalThis`, so no node types are needed. */
22
- interface ProcessLike {
23
- env?: Record<string, string | undefined>;
24
- }
25
-
26
- /** Highest valid TCP port number. */
27
- export const MAX_TCP_PORT = 65_535;
28
-
29
- /** One env var name, or several tried in order. */
30
- export type EnvKey = string | readonly string[];
31
-
32
- /** The ambient environment, or `{}` off-process (a browser). */
33
- function environment(): Record<string, string | undefined> {
34
- return (globalThis as { process?: ProcessLike }).process?.env ?? {};
35
- }
36
-
37
- /**
38
- * Detect the Databricks App runtime from its required environment shape.
39
- *
40
- * A valid app has a non-empty name, an HTTP(S) workspace host, and a valid
41
- * `DATABRICKS_APP_PORT`. Reads the ambient environment when none is supplied.
42
- */
43
- export function isAppEnv(source: Record<string, string | undefined> = environment()): boolean {
44
- const appName = source.DATABRICKS_APP_NAME?.trim();
45
- const host = source.DATABRICKS_HOST?.trim();
46
- const port = source.DATABRICKS_APP_PORT?.trim();
47
- if (!appName || !host || !port) return false;
48
-
49
- try {
50
- if (!["http:", "https:"].includes(new URL(host).protocol)) return false;
51
- } catch {
52
- return false;
53
- }
54
-
55
- const portNumber = toNumber(port);
56
- return (
57
- portNumber !== undefined &&
58
- Number.isInteger(portNumber) &&
59
- portNumber >= 1 &&
60
- portNumber <= MAX_TCP_PORT
61
- );
62
- }
63
-
64
- /**
65
- * The PRIMARY (current, non-deprecated) name in an {@link EnvKey}.
66
- *
67
- * Use this when naming a variable in a log line or error rather than indexing
68
- * `keys[0]`: an {@link EnvKey} may be a bare string, and `"TUNNEL_X"[0]` is the
69
- * character `"T"`, which produces a message naming a variable that does not
70
- * exist. Returns `""` only for an empty list, which no caller should have.
71
- *
72
- * @example
73
- * logger.warn(`${env.name(JWT_SECRET_ENV)} is not set`);
74
- */
75
- export function name(keys: EnvKey): string {
76
- return typeof keys === "string" ? keys : (keys[0] ?? "");
77
- }
78
-
79
- /**
80
- * First non-empty value among `keys`, trimmed, else `null`.
81
- *
82
- * Several names for one setting is the norm (a package-specific variable plus
83
- * the Databricks-standard one), so `keys` is order-sensitive: earlier names win.
84
- *
85
- * @example
86
- * env.text(["TEAMS_APP_ID", "MICROSOFT_APP_ID"]);
87
- */
88
- export function text(keys: EnvKey): string | null {
89
- const env = environment();
90
- for (const key of typeof keys === "string" ? [keys] : keys) {
91
- const value = trimToNull(env[key]);
92
- if (value !== null) return value;
93
- }
94
- return null;
95
- }
96
-
97
- /**
98
- * Resolve a string setting: `configured` when set and non-empty, else the first
99
- * non-empty variable among `keys`, else `null`.
100
- *
101
- * @example
102
- * env.string(config.host, "SMTP_HOST");
103
- */
104
- export function string(configured: unknown, keys: EnvKey): string | null {
105
- return trimToNull(configured) ?? text(keys);
106
- }
107
-
108
- /**
109
- * Resolve a boolean setting through {@link toBoolean}, so the loose spellings an
110
- * env var actually carries (`1`, `on`, `yes`, ...) are accepted. Returns
111
- * `undefined` when neither source is interpretable, letting the caller pick a
112
- * default with `??`.
113
- *
114
- * @example
115
- * env.boolean(config.fuzzy, "WEB_SEARCH_FUZZY") ?? true;
116
- */
117
- export function boolean(configured: unknown, keys: EnvKey): boolean | undefined {
118
- return toBoolean(configured) ?? toBoolean(text(keys));
119
- }
120
-
121
- /**
122
- * Resolve a positive-number setting that may be fractional (a score threshold,
123
- * a ratio): `configured` when it is a finite number greater than zero, else the
124
- * first variable among `keys` that parses that way, else `fallback`.
125
- *
126
- * {@link positiveInt} is the right choice for a count; this one keeps the
127
- * fraction, so a `0.4` threshold does not floor to `0`.
128
- *
129
- * @example
130
- * env.positiveNumber(config.fuzzyThreshold, "SEARCH_FUZZY_THRESHOLD", 0.4);
131
- */
132
- export function positiveNumber(configured: unknown, keys: EnvKey, fallback: number): number {
133
- return toPositiveNumber(configured) ?? toPositiveNumber(text(keys)) ?? fallback;
134
- }
135
-
136
- /**
137
- * Resolve a positive-integer setting (a port, timeout, page size, or cap):
138
- * `configured` when it is a finite number greater than zero, else the first
139
- * variable among `keys` that parses that way, else `fallback`. Floored, so a
140
- * fractional value can't leak into a count.
141
- *
142
- * A non-numeric or non-positive value is treated as absent rather than fatal -
143
- * these are ceilings and timeouts where a sane default beats a boot failure.
144
- *
145
- * @example
146
- * env.positiveInt(config.timeoutMs, "SEARCH_TIMEOUT_MS", 30_000);
147
- */
148
- export function positiveInt(configured: unknown, keys: EnvKey, fallback: number): number {
149
- return toPositiveInt(configured) ?? toPositiveInt(text(keys)) ?? fallback;
150
- }
151
-
152
- function toPositiveNumber(value: unknown): number | undefined {
153
- const parsed = toNumber(value);
154
- return parsed !== undefined && parsed > 0 ? parsed : undefined;
155
- }
156
-
157
- function toPositiveInt(value: unknown): number | undefined {
158
- const parsed = toPositiveNumber(value);
159
- return parsed === undefined ? undefined : Math.floor(parsed);
160
- }
161
-
162
- /**
163
- * Resolve a list setting through {@link parseList}, so an array from typed
164
- * config and a `"a, b c"` env string normalize identically. Returns `[]` when
165
- * neither source has entries.
166
- *
167
- * @example
168
- * env.list(config.modelFallbacks, "WEB_SEARCH_MODEL_FALLBACKS");
169
- */
170
- export function list(
171
- configured: string | readonly string[] | undefined | null,
172
- keys: EnvKey,
173
- transform?: (entry: string) => string,
174
- ): string[] {
175
- const fromConfig = parseList(configured, transform);
176
- return fromConfig.length > 0 ? fromConfig : parseList(text(keys), transform);
177
- }