@dbx-tools/shared-core 0.6.46 → 0.6.49

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
@@ -10,6 +10,7 @@ namespaces so call sites stay explicit:
10
10
  import {
11
11
  async,
12
12
  brand,
13
+ env,
13
14
  error,
14
15
  hash,
15
16
  http,
@@ -155,6 +156,38 @@ field off parsed JSON. `parseList()` normalizes a config value that may arrive a
155
156
  an array or as one comma/whitespace-separated env string, de-duplicating as it
156
157
  goes.
157
158
 
159
+ ## Configuration From The Environment
160
+
161
+ ```ts
162
+ const config = {
163
+ host: env.string(options.host, "SMTP_HOST"),
164
+ // Several names for one setting; earlier names win.
165
+ appId: env.string(options.appId, ["TEAMS_APP_ID", "MICROSOFT_APP_ID"]),
166
+ timeoutMs: env.positiveInt(options.timeoutMs, "SEARCH_TIMEOUT_MS", 30_000),
167
+ threshold: env.positiveNumber(options.threshold, "FUZZY_THRESHOLD", 0.4),
168
+ fuzzy: env.boolean(options.fuzzy, "SEARCH_FUZZY") ?? true,
169
+ fallbacks: env.list(options.fallbacks, "MODEL_FALLBACKS"),
170
+ };
171
+ ```
172
+
173
+ Every plugin resolves config the same way - the caller's typed value, else one or
174
+ more environment variables, else a default - and hand-writing that chain per
175
+ field is how `config.x ?? Number(process.env.X)` bugs get in. `positiveInt()`
176
+ floors, so a count cannot go fractional; `positiveNumber()` keeps the fraction
177
+ for a threshold or ratio. `boolean()` goes through `object.toBoolean()`, so the
178
+ loose spellings an env var actually carries (`1`, `on`, `yes`) are accepted, and
179
+ returns `undefined` when neither source is interpretable so `??` picks the
180
+ default. A name that is SET but unusable is not skipped for a later name in the
181
+ chain - ignoring what a deployment explicitly configured hides the mistake.
182
+
183
+ Browser-safe: `process` is reached through `globalThis` and guarded, so every
184
+ lookup simply misses off-process and the caller's fallback applies.
185
+
186
+ Pair it with `object.optional()` when a resolved value is an optional field -
187
+ `...object.optional("endpoint", env.string(...))` keeps an absent field ABSENT
188
+ rather than setting it to an explicit `undefined`, which
189
+ `exactOptionalPropertyTypes` rejects.
190
+
158
191
  ## Objects And Predicates
159
192
 
160
193
  ```ts
@@ -208,6 +241,13 @@ http.forEachHeaderValue(req, "authorization", (value) => {
208
241
  records, and plain `{ headers }` objects. `http.createFetchError()` turns a
209
242
  failed `Response` into an error with response text attached.
210
243
 
244
+ ## Intercepted Execution
245
+
246
+ `execution.directExecutor()` gives plugin-backed tools a no-plugin fallback with
247
+ the same success/failure shape as AppKit's executor. `execution.run()` merges the
248
+ plugin timeout signal with the caller's cancellation signal and centralizes
249
+ result unwrapping without imposing an AppKit dependency on shared code.
250
+
211
251
  ## Network Strings, Email, And CIDR
212
252
 
213
253
  ```ts
@@ -221,6 +261,36 @@ const internal = cidr ? net.ipInCidr("10.1.2.3", cidr) : false;
221
261
  `net.pathMatch()` compares path prefixes on segment boundaries. IP/CIDR helpers
222
262
  parse IPv4 and IPv6 into a shared bigint comparison model.
223
263
 
264
+ ## Allow-List Patterns
265
+
266
+ ```ts
267
+ // One matcher from a config array OR a delimited env string.
268
+ const forwardable = pattern.toPatternMatcher(["x-mastra-*", "/^x-trace-/", "x-tenant"]);
269
+
270
+ forwardable("x-mastra-model"); // true (glob)
271
+ forwardable("x-trace-parent"); // true (regex literal)
272
+ forwardable("x-tenant"); // true (literal, whole-string)
273
+ forwardable("x-forwarded-user"); // false
274
+ ```
275
+
276
+ Every configurable allow-list in this repo takes the same three shapes, so the
277
+ compilation lives here once: a `/regex/` literal (with optional flags), a
278
+ shell-style glob (`*`, `?`, anchored at both ends, all other characters escaped),
279
+ or a literal compared whole-string. Matching is case-insensitive by default -
280
+ what HTTP header names and email addresses both want - and `caseSensitive` opts
281
+ out.
282
+
283
+ An invalid regex is skipped with a warning instead of throwing, so one bad
284
+ config entry cannot stop a process from starting. No usable patterns yields a
285
+ matcher that always returns `false`; a caller that reads "no patterns" as "permit
286
+ everything" must say so itself. The result is a `predicate`, so it composes with
287
+ `.and()` / `.or()` / `.negate()`.
288
+
289
+ Reach for [`@dbx-tools/path`](../../node/path)'s `toPathMatcher` instead when
290
+ matching a filesystem path or URL path, where `/` is a segment boundary and `**`
291
+ is meaningful - that one is `minimatch`-backed. This module is deliberately
292
+ dependency-free so it stays usable in a browser bundle.
293
+
224
294
  ## Token Claims
225
295
 
226
296
  ```ts
@@ -267,10 +337,15 @@ without paying formatting cost when disabled.
267
337
  - `json` - non-throwing `parse()` and record-narrowing `parseRecord()`.
268
338
  - `string` - tokenization, slugs, identifiers, human labels, string coercion,
269
339
  config lists, descriptions, pluralization, and HTML escaping.
270
- - `object` - record checks, boolean coercion, deep equality, shape types, and
271
- lazy sequence transforms + collection helpers.
340
+ - `object` - record checks, boolean coercion, present-only field spreading, deep
341
+ equality, shape types, and lazy sequence transforms + collection helpers.
342
+ - `env` - config-over-environment resolution: strings, booleans, positive
343
+ numbers/integers, and lists, with env-name fallback chains.
272
344
  - `predicate` - composable boolean/type predicates.
345
+ - `pattern` - literal / glob / `/regex/` allow-list matching compiled to a
346
+ predicate.
273
347
  - `http` - header iteration, cookie parsing, and fetch error creation.
348
+ - `execution` - direct executor fallback, cancellation merging, and result unwrapping.
274
349
  - `net` - URL building, email parsing, path matching, IP/CIDR helpers.
275
350
  - `token` - JWT payload and scope readers.
276
351
  - `functionModule` - memoization.
package/index.ts CHANGED
@@ -4,7 +4,9 @@
4
4
 
5
5
  export * as async from "./src/async.ts";
6
6
  export * as brand from "./src/brand.ts";
7
+ export * as env from "./src/env.ts";
7
8
  export * as error from "./src/error.ts";
9
+ export * as execution from "./src/execution.ts";
8
10
  export * as functionModule from "./src/function.ts";
9
11
  export * as hash from "./src/hash.ts";
10
12
  export * as http from "./src/http.ts";
@@ -12,17 +14,22 @@ export * as json from "./src/json.ts";
12
14
  export * as log from "./src/log.ts";
13
15
  export * as net from "./src/net.ts";
14
16
  export * as object from "./src/object.ts";
17
+ export * as pattern from "./src/pattern.ts";
15
18
  export * as predicate from "./src/predicate.ts";
16
19
  export * as string from "./src/string.ts";
17
20
  export * as token from "./src/token.ts";
18
21
  export type { PollContext, PollProducer, PollOptions } from "./src/async.ts";
19
22
  export { DEFAULT_BRAND_ASSETS, BrandAssetSetSchema, BrandColorsSchema, BrandVoiceSchema, BrandContextSchema, defaultBrandContext } from "./src/brand.ts";
20
23
  export type { BrandContext, BrandContextInput, BrandAssetSet } from "./src/brand.ts";
24
+ export type { EnvKey } from "./src/env.ts";
21
25
  export type { ErrorContext } from "./src/error.ts";
26
+ export type { ExecutionResult, Executor, ExecutionFailure, RunOptions } from "./src/execution.ts";
22
27
  export type { MemoizeOptions } from "./src/function.ts";
23
28
  export type { HeaderLike } from "./src/http.ts";
24
29
  export type { LogLevel, Logger } from "./src/log.ts";
25
30
  export type { UrlLike, IpVersion, ParsedIp, Cidr, UrlBuilder, ParseEmailsOptions } from "./src/net.ts";
26
31
  export type { Sequence, Container, Collection, OneOrMany, NameLike, NonFunctionKeys, DeepEqualComparator } from "./src/object.ts";
32
+ export type { PatternOptions } from "./src/pattern.ts";
27
33
  export type { PredicateFunction, TypePredicateFunction, PredicateInput, Predicate } from "./src/predicate.ts";
28
34
  export type { TokenizeOptions, KeyOptions, IdentifierOptions, Description } from "./src/string.ts";
35
+ export { ACCESS_TOKEN_HEADER, USER_ID_HEADER, USER_EMAIL_HEADER } from "./src/token.ts";
package/lib/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export * as async from "./src/async.ts";
2
2
  export * as brand from "./src/brand.ts";
3
+ export * as env from "./src/env.ts";
3
4
  export * as error from "./src/error.ts";
5
+ export * as execution from "./src/execution.ts";
4
6
  export * as functionModule from "./src/function.ts";
5
7
  export * as hash from "./src/hash.ts";
6
8
  export * as http from "./src/http.ts";
@@ -8,17 +10,22 @@ export * as json from "./src/json.ts";
8
10
  export * as log from "./src/log.ts";
9
11
  export * as net from "./src/net.ts";
10
12
  export * as object from "./src/object.ts";
13
+ export * as pattern from "./src/pattern.ts";
11
14
  export * as predicate from "./src/predicate.ts";
12
15
  export * as string from "./src/string.ts";
13
16
  export * as token from "./src/token.ts";
14
17
  export type { PollContext, PollProducer, PollOptions } from "./src/async.ts";
15
18
  export { DEFAULT_BRAND_ASSETS, BrandAssetSetSchema, BrandColorsSchema, BrandVoiceSchema, BrandContextSchema, defaultBrandContext } from "./src/brand.ts";
16
19
  export type { BrandContext, BrandContextInput, BrandAssetSet } from "./src/brand.ts";
20
+ export type { EnvKey } from "./src/env.ts";
17
21
  export type { ErrorContext } from "./src/error.ts";
22
+ export type { ExecutionResult, Executor, ExecutionFailure, RunOptions } from "./src/execution.ts";
18
23
  export type { MemoizeOptions } from "./src/function.ts";
19
24
  export type { HeaderLike } from "./src/http.ts";
20
25
  export type { LogLevel, Logger } from "./src/log.ts";
21
26
  export type { UrlLike, IpVersion, ParsedIp, Cidr, UrlBuilder, ParseEmailsOptions } from "./src/net.ts";
22
27
  export type { Sequence, Container, Collection, OneOrMany, NameLike, NonFunctionKeys, DeepEqualComparator } from "./src/object.ts";
28
+ export type { PatternOptions } from "./src/pattern.ts";
23
29
  export type { PredicateFunction, TypePredicateFunction, PredicateInput, Predicate } from "./src/predicate.ts";
24
30
  export type { TokenizeOptions, KeyOptions, IdentifierOptions, Description } from "./src/string.ts";
31
+ export { ACCESS_TOKEN_HEADER, USER_ID_HEADER, USER_EMAIL_HEADER } from "./src/token.ts";
package/lib/index.js CHANGED
@@ -3,7 +3,9 @@
3
3
  // Hand edits are overwritten on the next watch; this file is read-only.
4
4
  export * as async from "./src/async.js";
5
5
  export * as brand from "./src/brand.js";
6
+ export * as env from "./src/env.js";
6
7
  export * as error from "./src/error.js";
8
+ export * as execution from "./src/execution.js";
7
9
  export * as functionModule from "./src/function.js";
8
10
  export * as hash from "./src/hash.js";
9
11
  export * as http from "./src/http.js";
@@ -11,8 +13,10 @@ export * as json from "./src/json.js";
11
13
  export * as log from "./src/log.js";
12
14
  export * as net from "./src/net.js";
13
15
  export * as object from "./src/object.js";
16
+ export * as pattern from "./src/pattern.js";
14
17
  export * as predicate from "./src/predicate.js";
15
18
  export * as string from "./src/string.js";
16
19
  export * as token from "./src/token.js";
17
20
  export { DEFAULT_BRAND_ASSETS, BrandAssetSetSchema, BrandColorsSchema, BrandVoiceSchema, BrandContextSchema, defaultBrandContext } from "./src/brand.js";
18
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwyQ0FBMkM7QUFDM0MsbURBQW1EO0FBQ25ELHdFQUF3RTtBQUV4RSxPQUFPLEtBQUssS0FBSyxNQUFNLGdCQUFnQixDQUFDO0FBQ3hDLE9BQU8sS0FBSyxLQUFLLE1BQU0sZ0JBQWdCLENBQUM7QUFDeEMsT0FBTyxLQUFLLEtBQUssTUFBTSxnQkFBZ0IsQ0FBQztBQUN4QyxPQUFPLEtBQUssY0FBYyxNQUFNLG1CQUFtQixDQUFDO0FBQ3BELE9BQU8sS0FBSyxJQUFJLE1BQU0sZUFBZSxDQUFDO0FBQ3RDLE9BQU8sS0FBSyxJQUFJLE1BQU0sZUFBZSxDQUFDO0FBQ3RDLE9BQU8sS0FBSyxJQUFJLE1BQU0sZUFBZSxDQUFDO0FBQ3RDLE9BQU8sS0FBSyxHQUFHLE1BQU0sY0FBYyxDQUFDO0FBQ3BDLE9BQU8sS0FBSyxHQUFHLE1BQU0sY0FBYyxDQUFDO0FBQ3BDLE9BQU8sS0FBSyxNQUFNLE1BQU0saUJBQWlCLENBQUM7QUFDMUMsT0FBTyxLQUFLLFNBQVMsTUFBTSxvQkFBb0IsQ0FBQztBQUNoRCxPQUFPLEtBQUssTUFBTSxNQUFNLGlCQUFpQixDQUFDO0FBQzFDLE9BQU8sS0FBSyxLQUFLLE1BQU0sZ0JBQWdCLENBQUM7QUFFeEMsT0FBTyxFQUFFLG9CQUFvQixFQUFFLG1CQUFtQixFQUFFLGlCQUFpQixFQUFFLGdCQUFnQixFQUFFLGtCQUFrQixFQUFFLG1CQUFtQixFQUFFLE1BQU0sZ0JBQWdCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBHRU5FUkFURUQgYnkgcHJvamVuIHdhdGNoIC0gRE8gTk9UIEVESVQuXG4vLyBSZWdlbmVyYXRlZCBmcm9tIHRoZSBleHBvcnRpbmcgbW9kdWxlcyBpbiAuL3NyYy5cbi8vIEhhbmQgZWRpdHMgYXJlIG92ZXJ3cml0dGVuIG9uIHRoZSBuZXh0IHdhdGNoOyB0aGlzIGZpbGUgaXMgcmVhZC1vbmx5LlxuXG5leHBvcnQgKiBhcyBhc3luYyBmcm9tIFwiLi9zcmMvYXN5bmMudHNcIjtcbmV4cG9ydCAqIGFzIGJyYW5kIGZyb20gXCIuL3NyYy9icmFuZC50c1wiO1xuZXhwb3J0ICogYXMgZXJyb3IgZnJvbSBcIi4vc3JjL2Vycm9yLnRzXCI7XG5leHBvcnQgKiBhcyBmdW5jdGlvbk1vZHVsZSBmcm9tIFwiLi9zcmMvZnVuY3Rpb24udHNcIjtcbmV4cG9ydCAqIGFzIGhhc2ggZnJvbSBcIi4vc3JjL2hhc2gudHNcIjtcbmV4cG9ydCAqIGFzIGh0dHAgZnJvbSBcIi4vc3JjL2h0dHAudHNcIjtcbmV4cG9ydCAqIGFzIGpzb24gZnJvbSBcIi4vc3JjL2pzb24udHNcIjtcbmV4cG9ydCAqIGFzIGxvZyBmcm9tIFwiLi9zcmMvbG9nLnRzXCI7XG5leHBvcnQgKiBhcyBuZXQgZnJvbSBcIi4vc3JjL25ldC50c1wiO1xuZXhwb3J0ICogYXMgb2JqZWN0IGZyb20gXCIuL3NyYy9vYmplY3QudHNcIjtcbmV4cG9ydCAqIGFzIHByZWRpY2F0ZSBmcm9tIFwiLi9zcmMvcHJlZGljYXRlLnRzXCI7XG5leHBvcnQgKiBhcyBzdHJpbmcgZnJvbSBcIi4vc3JjL3N0cmluZy50c1wiO1xuZXhwb3J0ICogYXMgdG9rZW4gZnJvbSBcIi4vc3JjL3Rva2VuLnRzXCI7XG5leHBvcnQgdHlwZSB7IFBvbGxDb250ZXh0LCBQb2xsUHJvZHVjZXIsIFBvbGxPcHRpb25zIH0gZnJvbSBcIi4vc3JjL2FzeW5jLnRzXCI7XG5leHBvcnQgeyBERUZBVUxUX0JSQU5EX0FTU0VUUywgQnJhbmRBc3NldFNldFNjaGVtYSwgQnJhbmRDb2xvcnNTY2hlbWEsIEJyYW5kVm9pY2VTY2hlbWEsIEJyYW5kQ29udGV4dFNjaGVtYSwgZGVmYXVsdEJyYW5kQ29udGV4dCB9IGZyb20gXCIuL3NyYy9icmFuZC50c1wiO1xuZXhwb3J0IHR5cGUgeyBCcmFuZENvbnRleHQsIEJyYW5kQ29udGV4dElucHV0LCBCcmFuZEFzc2V0U2V0IH0gZnJvbSBcIi4vc3JjL2JyYW5kLnRzXCI7XG5leHBvcnQgdHlwZSB7IEVycm9yQ29udGV4dCB9IGZyb20gXCIuL3NyYy9lcnJvci50c1wiO1xuZXhwb3J0IHR5cGUgeyBNZW1vaXplT3B0aW9ucyB9IGZyb20gXCIuL3NyYy9mdW5jdGlvbi50c1wiO1xuZXhwb3J0IHR5cGUgeyBIZWFkZXJMaWtlIH0gZnJvbSBcIi4vc3JjL2h0dHAudHNcIjtcbmV4cG9ydCB0eXBlIHsgTG9nTGV2ZWwsIExvZ2dlciB9IGZyb20gXCIuL3NyYy9sb2cudHNcIjtcbmV4cG9ydCB0eXBlIHsgVXJsTGlrZSwgSXBWZXJzaW9uLCBQYXJzZWRJcCwgQ2lkciwgVXJsQnVpbGRlciwgUGFyc2VFbWFpbHNPcHRpb25zIH0gZnJvbSBcIi4vc3JjL25ldC50c1wiO1xuZXhwb3J0IHR5cGUgeyBTZXF1ZW5jZSwgQ29udGFpbmVyLCBDb2xsZWN0aW9uLCBPbmVPck1hbnksIE5hbWVMaWtlLCBOb25GdW5jdGlvbktleXMsIERlZXBFcXVhbENvbXBhcmF0b3IgfSBmcm9tIFwiLi9zcmMvb2JqZWN0LnRzXCI7XG5leHBvcnQgdHlwZSB7IFByZWRpY2F0ZUZ1bmN0aW9uLCBUeXBlUHJlZGljYXRlRnVuY3Rpb24sIFByZWRpY2F0ZUlucHV0LCBQcmVkaWNhdGUgfSBmcm9tIFwiLi9zcmMvcHJlZGljYXRlLnRzXCI7XG5leHBvcnQgdHlwZSB7IFRva2VuaXplT3B0aW9ucywgS2V5T3B0aW9ucywgSWRlbnRpZmllck9wdGlvbnMsIERlc2NyaXB0aW9uIH0gZnJvbSBcIi4vc3JjL3N0cmluZy50c1wiO1xuIl19
21
+ export { ACCESS_TOKEN_HEADER, USER_ID_HEADER, USER_EMAIL_HEADER } from "./src/token.js";
22
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwyQ0FBMkM7QUFDM0MsbURBQW1EO0FBQ25ELHdFQUF3RTtBQUV4RSxPQUFPLEtBQUssS0FBSyxNQUFNLGdCQUFnQixDQUFDO0FBQ3hDLE9BQU8sS0FBSyxLQUFLLE1BQU0sZ0JBQWdCLENBQUM7QUFDeEMsT0FBTyxLQUFLLEdBQUcsTUFBTSxjQUFjLENBQUM7QUFDcEMsT0FBTyxLQUFLLEtBQUssTUFBTSxnQkFBZ0IsQ0FBQztBQUN4QyxPQUFPLEtBQUssU0FBUyxNQUFNLG9CQUFvQixDQUFDO0FBQ2hELE9BQU8sS0FBSyxjQUFjLE1BQU0sbUJBQW1CLENBQUM7QUFDcEQsT0FBTyxLQUFLLElBQUksTUFBTSxlQUFlLENBQUM7QUFDdEMsT0FBTyxLQUFLLElBQUksTUFBTSxlQUFlLENBQUM7QUFDdEMsT0FBTyxLQUFLLElBQUksTUFBTSxlQUFlLENBQUM7QUFDdEMsT0FBTyxLQUFLLEdBQUcsTUFBTSxjQUFjLENBQUM7QUFDcEMsT0FBTyxLQUFLLEdBQUcsTUFBTSxjQUFjLENBQUM7QUFDcEMsT0FBTyxLQUFLLE1BQU0sTUFBTSxpQkFBaUIsQ0FBQztBQUMxQyxPQUFPLEtBQUssT0FBTyxNQUFNLGtCQUFrQixDQUFDO0FBQzVDLE9BQU8sS0FBSyxTQUFTLE1BQU0sb0JBQW9CLENBQUM7QUFDaEQsT0FBTyxLQUFLLE1BQU0sTUFBTSxpQkFBaUIsQ0FBQztBQUMxQyxPQUFPLEtBQUssS0FBSyxNQUFNLGdCQUFnQixDQUFDO0FBRXhDLE9BQU8sRUFBRSxvQkFBb0IsRUFBRSxtQkFBbUIsRUFBRSxpQkFBaUIsRUFBRSxnQkFBZ0IsRUFBRSxrQkFBa0IsRUFBRSxtQkFBbUIsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBYXpKLE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxjQUFjLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIEdFTkVSQVRFRCBieSBwcm9qZW4gd2F0Y2ggLSBETyBOT1QgRURJVC5cbi8vIFJlZ2VuZXJhdGVkIGZyb20gdGhlIGV4cG9ydGluZyBtb2R1bGVzIGluIC4vc3JjLlxuLy8gSGFuZCBlZGl0cyBhcmUgb3ZlcndyaXR0ZW4gb24gdGhlIG5leHQgd2F0Y2g7IHRoaXMgZmlsZSBpcyByZWFkLW9ubHkuXG5cbmV4cG9ydCAqIGFzIGFzeW5jIGZyb20gXCIuL3NyYy9hc3luYy50c1wiO1xuZXhwb3J0ICogYXMgYnJhbmQgZnJvbSBcIi4vc3JjL2JyYW5kLnRzXCI7XG5leHBvcnQgKiBhcyBlbnYgZnJvbSBcIi4vc3JjL2Vudi50c1wiO1xuZXhwb3J0ICogYXMgZXJyb3IgZnJvbSBcIi4vc3JjL2Vycm9yLnRzXCI7XG5leHBvcnQgKiBhcyBleGVjdXRpb24gZnJvbSBcIi4vc3JjL2V4ZWN1dGlvbi50c1wiO1xuZXhwb3J0ICogYXMgZnVuY3Rpb25Nb2R1bGUgZnJvbSBcIi4vc3JjL2Z1bmN0aW9uLnRzXCI7XG5leHBvcnQgKiBhcyBoYXNoIGZyb20gXCIuL3NyYy9oYXNoLnRzXCI7XG5leHBvcnQgKiBhcyBodHRwIGZyb20gXCIuL3NyYy9odHRwLnRzXCI7XG5leHBvcnQgKiBhcyBqc29uIGZyb20gXCIuL3NyYy9qc29uLnRzXCI7XG5leHBvcnQgKiBhcyBsb2cgZnJvbSBcIi4vc3JjL2xvZy50c1wiO1xuZXhwb3J0ICogYXMgbmV0IGZyb20gXCIuL3NyYy9uZXQudHNcIjtcbmV4cG9ydCAqIGFzIG9iamVjdCBmcm9tIFwiLi9zcmMvb2JqZWN0LnRzXCI7XG5leHBvcnQgKiBhcyBwYXR0ZXJuIGZyb20gXCIuL3NyYy9wYXR0ZXJuLnRzXCI7XG5leHBvcnQgKiBhcyBwcmVkaWNhdGUgZnJvbSBcIi4vc3JjL3ByZWRpY2F0ZS50c1wiO1xuZXhwb3J0ICogYXMgc3RyaW5nIGZyb20gXCIuL3NyYy9zdHJpbmcudHNcIjtcbmV4cG9ydCAqIGFzIHRva2VuIGZyb20gXCIuL3NyYy90b2tlbi50c1wiO1xuZXhwb3J0IHR5cGUgeyBQb2xsQ29udGV4dCwgUG9sbFByb2R1Y2VyLCBQb2xsT3B0aW9ucyB9IGZyb20gXCIuL3NyYy9hc3luYy50c1wiO1xuZXhwb3J0IHsgREVGQVVMVF9CUkFORF9BU1NFVFMsIEJyYW5kQXNzZXRTZXRTY2hlbWEsIEJyYW5kQ29sb3JzU2NoZW1hLCBCcmFuZFZvaWNlU2NoZW1hLCBCcmFuZENvbnRleHRTY2hlbWEsIGRlZmF1bHRCcmFuZENvbnRleHQgfSBmcm9tIFwiLi9zcmMvYnJhbmQudHNcIjtcbmV4cG9ydCB0eXBlIHsgQnJhbmRDb250ZXh0LCBCcmFuZENvbnRleHRJbnB1dCwgQnJhbmRBc3NldFNldCB9IGZyb20gXCIuL3NyYy9icmFuZC50c1wiO1xuZXhwb3J0IHR5cGUgeyBFbnZLZXkgfSBmcm9tIFwiLi9zcmMvZW52LnRzXCI7XG5leHBvcnQgdHlwZSB7IEVycm9yQ29udGV4dCB9IGZyb20gXCIuL3NyYy9lcnJvci50c1wiO1xuZXhwb3J0IHR5cGUgeyBFeGVjdXRpb25SZXN1bHQsIEV4ZWN1dG9yLCBFeGVjdXRpb25GYWlsdXJlLCBSdW5PcHRpb25zIH0gZnJvbSBcIi4vc3JjL2V4ZWN1dGlvbi50c1wiO1xuZXhwb3J0IHR5cGUgeyBNZW1vaXplT3B0aW9ucyB9IGZyb20gXCIuL3NyYy9mdW5jdGlvbi50c1wiO1xuZXhwb3J0IHR5cGUgeyBIZWFkZXJMaWtlIH0gZnJvbSBcIi4vc3JjL2h0dHAudHNcIjtcbmV4cG9ydCB0eXBlIHsgTG9nTGV2ZWwsIExvZ2dlciB9IGZyb20gXCIuL3NyYy9sb2cudHNcIjtcbmV4cG9ydCB0eXBlIHsgVXJsTGlrZSwgSXBWZXJzaW9uLCBQYXJzZWRJcCwgQ2lkciwgVXJsQnVpbGRlciwgUGFyc2VFbWFpbHNPcHRpb25zIH0gZnJvbSBcIi4vc3JjL25ldC50c1wiO1xuZXhwb3J0IHR5cGUgeyBTZXF1ZW5jZSwgQ29udGFpbmVyLCBDb2xsZWN0aW9uLCBPbmVPck1hbnksIE5hbWVMaWtlLCBOb25GdW5jdGlvbktleXMsIERlZXBFcXVhbENvbXBhcmF0b3IgfSBmcm9tIFwiLi9zcmMvb2JqZWN0LnRzXCI7XG5leHBvcnQgdHlwZSB7IFBhdHRlcm5PcHRpb25zIH0gZnJvbSBcIi4vc3JjL3BhdHRlcm4udHNcIjtcbmV4cG9ydCB0eXBlIHsgUHJlZGljYXRlRnVuY3Rpb24sIFR5cGVQcmVkaWNhdGVGdW5jdGlvbiwgUHJlZGljYXRlSW5wdXQsIFByZWRpY2F0ZSB9IGZyb20gXCIuL3NyYy9wcmVkaWNhdGUudHNcIjtcbmV4cG9ydCB0eXBlIHsgVG9rZW5pemVPcHRpb25zLCBLZXlPcHRpb25zLCBJZGVudGlmaWVyT3B0aW9ucywgRGVzY3JpcHRpb24gfSBmcm9tIFwiLi9zcmMvc3RyaW5nLnRzXCI7XG5leHBvcnQgeyBBQ0NFU1NfVE9LRU5fSEVBREVSLCBVU0VSX0lEX0hFQURFUiwgVVNFUl9FTUFJTF9IRUFERVIgfSBmcm9tIFwiLi9zcmMvdG9rZW4udHNcIjtcbiJdfQ==
@@ -0,0 +1,80 @@
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
+ /** One env var name, or several tried in order. */
18
+ export type EnvKey = string | readonly string[];
19
+ /**
20
+ * First non-empty value among `keys`, trimmed, else `null`.
21
+ *
22
+ * Several names for one setting is the norm (a package-specific variable plus
23
+ * the Databricks-standard one), so `keys` is order-sensitive: earlier names win.
24
+ *
25
+ * @example
26
+ * env.text(["TEAMS_APP_ID", "MICROSOFT_APP_ID"]);
27
+ */
28
+ export declare function text(keys: EnvKey): string | null;
29
+ /**
30
+ * Resolve a string setting: `configured` when set and non-empty, else the first
31
+ * non-empty variable among `keys`, else `null`.
32
+ *
33
+ * @example
34
+ * env.string(config.host, "SMTP_HOST");
35
+ */
36
+ export declare function string(configured: unknown, keys: EnvKey): string | null;
37
+ /**
38
+ * Resolve a boolean setting through {@link toBoolean}, so the loose spellings an
39
+ * env var actually carries (`1`, `on`, `yes`, ...) are accepted. Returns
40
+ * `undefined` when neither source is interpretable, letting the caller pick a
41
+ * default with `??`.
42
+ *
43
+ * @example
44
+ * env.boolean(config.fuzzy, "WEB_SEARCH_FUZZY") ?? true;
45
+ */
46
+ export declare function boolean(configured: unknown, keys: EnvKey): boolean | undefined;
47
+ /**
48
+ * Resolve a positive-number setting that may be fractional (a score threshold,
49
+ * a ratio): `configured` when it is a finite number greater than zero, else the
50
+ * first variable among `keys` that parses that way, else `fallback`.
51
+ *
52
+ * {@link positiveInt} is the right choice for a count; this one keeps the
53
+ * fraction, so a `0.4` threshold does not floor to `0`.
54
+ *
55
+ * @example
56
+ * env.positiveNumber(config.fuzzyThreshold, "SEARCH_FUZZY_THRESHOLD", 0.4);
57
+ */
58
+ export declare function positiveNumber(configured: unknown, keys: EnvKey, fallback: number): number;
59
+ /**
60
+ * Resolve a positive-integer setting (a port, timeout, page size, or cap):
61
+ * `configured` when it is a finite number greater than zero, else the first
62
+ * variable among `keys` that parses that way, else `fallback`. Floored, so a
63
+ * fractional value can't leak into a count.
64
+ *
65
+ * A non-numeric or non-positive value is treated as absent rather than fatal -
66
+ * these are ceilings and timeouts where a sane default beats a boot failure.
67
+ *
68
+ * @example
69
+ * env.positiveInt(config.timeoutMs, "SEARCH_TIMEOUT_MS", 30_000);
70
+ */
71
+ export declare function positiveInt(configured: unknown, keys: EnvKey, fallback: number): number;
72
+ /**
73
+ * Resolve a list setting through {@link parseList}, so an array from typed
74
+ * config and a `"a, b c"` env string normalize identically. Returns `[]` when
75
+ * neither source has entries.
76
+ *
77
+ * @example
78
+ * env.list(config.modelFallbacks, "WEB_SEARCH_MODEL_FALLBACKS");
79
+ */
80
+ export declare function list(configured: string | readonly string[] | undefined | null, keys: EnvKey, transform?: (entry: string) => string): string[];
package/lib/src/env.js ADDED
@@ -0,0 +1,114 @@
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 } from "./object.js";
18
+ import { parseList, trimToNull } from "./string.js";
19
+ /** The ambient environment, or `{}` off-process (a browser). */
20
+ function environment() {
21
+ return globalThis.process?.env ?? {};
22
+ }
23
+ /**
24
+ * First non-empty value among `keys`, trimmed, else `null`.
25
+ *
26
+ * Several names for one setting is the norm (a package-specific variable plus
27
+ * the Databricks-standard one), so `keys` is order-sensitive: earlier names win.
28
+ *
29
+ * @example
30
+ * env.text(["TEAMS_APP_ID", "MICROSOFT_APP_ID"]);
31
+ */
32
+ export function text(keys) {
33
+ const env = environment();
34
+ for (const key of typeof keys === "string" ? [keys] : keys) {
35
+ const value = trimToNull(env[key]);
36
+ if (value !== null)
37
+ return value;
38
+ }
39
+ return null;
40
+ }
41
+ /**
42
+ * Resolve a string setting: `configured` when set and non-empty, else the first
43
+ * non-empty variable among `keys`, else `null`.
44
+ *
45
+ * @example
46
+ * env.string(config.host, "SMTP_HOST");
47
+ */
48
+ export function string(configured, keys) {
49
+ return trimToNull(configured) ?? text(keys);
50
+ }
51
+ /**
52
+ * Resolve a boolean setting through {@link toBoolean}, so the loose spellings an
53
+ * env var actually carries (`1`, `on`, `yes`, ...) are accepted. Returns
54
+ * `undefined` when neither source is interpretable, letting the caller pick a
55
+ * default with `??`.
56
+ *
57
+ * @example
58
+ * env.boolean(config.fuzzy, "WEB_SEARCH_FUZZY") ?? true;
59
+ */
60
+ export function boolean(configured, keys) {
61
+ return toBoolean(configured) ?? toBoolean(text(keys));
62
+ }
63
+ /**
64
+ * Resolve a positive-number setting that may be fractional (a score threshold,
65
+ * a ratio): `configured` when it is a finite number greater than zero, else the
66
+ * first variable among `keys` that parses that way, else `fallback`.
67
+ *
68
+ * {@link positiveInt} is the right choice for a count; this one keeps the
69
+ * fraction, so a `0.4` threshold does not floor to `0`.
70
+ *
71
+ * @example
72
+ * env.positiveNumber(config.fuzzyThreshold, "SEARCH_FUZZY_THRESHOLD", 0.4);
73
+ */
74
+ export function positiveNumber(configured, keys, fallback) {
75
+ return toPositiveNumber(configured) ?? toPositiveNumber(text(keys)) ?? fallback;
76
+ }
77
+ /**
78
+ * Resolve a positive-integer setting (a port, timeout, page size, or cap):
79
+ * `configured` when it is a finite number greater than zero, else the first
80
+ * variable among `keys` that parses that way, else `fallback`. Floored, so a
81
+ * fractional value can't leak into a count.
82
+ *
83
+ * A non-numeric or non-positive value is treated as absent rather than fatal -
84
+ * these are ceilings and timeouts where a sane default beats a boot failure.
85
+ *
86
+ * @example
87
+ * env.positiveInt(config.timeoutMs, "SEARCH_TIMEOUT_MS", 30_000);
88
+ */
89
+ export function positiveInt(configured, keys, fallback) {
90
+ return toPositiveInt(configured) ?? toPositiveInt(text(keys)) ?? fallback;
91
+ }
92
+ function toPositiveNumber(value) {
93
+ if (value === null || value === undefined || value === "")
94
+ return undefined;
95
+ const parsed = Number(value);
96
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
97
+ }
98
+ function toPositiveInt(value) {
99
+ const parsed = toPositiveNumber(value);
100
+ return parsed === undefined ? undefined : Math.floor(parsed);
101
+ }
102
+ /**
103
+ * Resolve a list setting through {@link parseList}, so an array from typed
104
+ * config and a `"a, b c"` env string normalize identically. Returns `[]` when
105
+ * neither source has entries.
106
+ *
107
+ * @example
108
+ * env.list(config.modelFallbacks, "WEB_SEARCH_MODEL_FALLBACKS");
109
+ */
110
+ export function list(configured, keys, transform) {
111
+ const fromConfig = parseList(configured, transform);
112
+ return fromConfig.length > 0 ? fromConfig : parseList(text(keys), transform);
113
+ }
114
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZW52LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2Vudi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7O0dBZUc7QUFFSCxPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBQ3hDLE9BQU8sRUFBRSxTQUFTLEVBQUUsVUFBVSxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBVXBELGdFQUFnRTtBQUNoRSxTQUFTLFdBQVc7SUFDbEIsT0FBUSxVQUF3QyxDQUFDLE9BQU8sRUFBRSxHQUFHLElBQUksRUFBRSxDQUFDO0FBQ3RFLENBQUM7QUFFRDs7Ozs7Ozs7R0FRRztBQUNILE1BQU0sVUFBVSxJQUFJLENBQUMsSUFBWTtJQUMvQixNQUFNLEdBQUcsR0FBRyxXQUFXLEVBQUUsQ0FBQztJQUMxQixLQUFLLE1BQU0sR0FBRyxJQUFJLE9BQU8sSUFBSSxLQUFLLFFBQVEsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxFQUFFLENBQUM7UUFDM0QsTUFBTSxLQUFLLEdBQUcsVUFBVSxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO1FBQ25DLElBQUksS0FBSyxLQUFLLElBQUk7WUFBRSxPQUFPLEtBQUssQ0FBQztJQUNuQyxDQUFDO0lBQ0QsT0FBTyxJQUFJLENBQUM7QUFDZCxDQUFDO0FBRUQ7Ozs7OztHQU1HO0FBQ0gsTUFBTSxVQUFVLE1BQU0sQ0FBQyxVQUFtQixFQUFFLElBQVk7SUFDdEQsT0FBTyxVQUFVLENBQUMsVUFBVSxDQUFDLElBQUksSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO0FBQzlDLENBQUM7QUFFRDs7Ozs7Ozs7R0FRRztBQUNILE1BQU0sVUFBVSxPQUFPLENBQUMsVUFBbUIsRUFBRSxJQUFZO0lBQ3ZELE9BQU8sU0FBUyxDQUFDLFVBQVUsQ0FBQyxJQUFJLFNBQVMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztBQUN4RCxDQUFDO0FBRUQ7Ozs7Ozs7Ozs7R0FVRztBQUNILE1BQU0sVUFBVSxjQUFjLENBQUMsVUFBbUIsRUFBRSxJQUFZLEVBQUUsUUFBZ0I7SUFDaEYsT0FBTyxnQkFBZ0IsQ0FBQyxVQUFVLENBQUMsSUFBSSxnQkFBZ0IsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUMsSUFBSSxRQUFRLENBQUM7QUFDbEYsQ0FBQztBQUVEOzs7Ozs7Ozs7OztHQVdHO0FBQ0gsTUFBTSxVQUFVLFdBQVcsQ0FBQyxVQUFtQixFQUFFLElBQVksRUFBRSxRQUFnQjtJQUM3RSxPQUFPLGFBQWEsQ0FBQyxVQUFVLENBQUMsSUFBSSxhQUFhLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDLElBQUksUUFBUSxDQUFDO0FBQzVFLENBQUM7QUFFRCxTQUFTLGdCQUFnQixDQUFDLEtBQWM7SUFDdEMsSUFBSSxLQUFLLEtBQUssSUFBSSxJQUFJLEtBQUssS0FBSyxTQUFTLElBQUksS0FBSyxLQUFLLEVBQUU7UUFBRSxPQUFPLFNBQVMsQ0FBQztJQUM1RSxNQUFNLE1BQU0sR0FBRyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDN0IsT0FBTyxNQUFNLENBQUMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxJQUFJLE1BQU0sR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDO0FBQ3BFLENBQUM7QUFFRCxTQUFTLGFBQWEsQ0FBQyxLQUFjO0lBQ25DLE1BQU0sTUFBTSxHQUFHLGdCQUFnQixDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQ3ZDLE9BQU8sTUFBTSxLQUFLLFNBQVMsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQyxDQUFDO0FBQy9ELENBQUM7QUFFRDs7Ozs7OztHQU9HO0FBQ0gsTUFBTSxVQUFVLElBQUksQ0FDbEIsVUFBeUQsRUFDekQsSUFBWSxFQUNaLFNBQXFDO0lBRXJDLE1BQU0sVUFBVSxHQUFHLFNBQVMsQ0FBQyxVQUFVLEVBQUUsU0FBUyxDQUFDLENBQUM7SUFDcEQsT0FBTyxVQUFVLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFLFNBQVMsQ0FBQyxDQUFDO0FBQy9FLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIFJlYWRpbmcgY29uZmlndXJhdGlvbiBvdXQgb2YgdGhlIGVudmlyb25tZW50LlxuICpcbiAqIEV2ZXJ5IHBsdWdpbiBjb25maWcgaW4gdGhpcyByZXBvIHJlc29sdmVzIHRoZSBzYW1lIHdheTogdGFrZSB0aGUgdHlwZWRcbiAqIGNvbmZpZyB2YWx1ZSB3aGVuIHRoZSBjYWxsZXIgc2V0IG9uZSwgZWxzZSBmYWxsIGJhY2sgdG8gb25lIG9yIG1vcmVcbiAqIGVudmlyb25tZW50IHZhcmlhYmxlcywgZWxzZSBhIGRlZmF1bHQuIFdyaXR0ZW4gYnkgaGFuZCB0aGF0IGJlY29tZXMgYVxuICogYGNvbmZpZy54ID8/IE51bWJlcihwcm9jZXNzLmVudi5YKWAgY2hhaW4gcGVyIGZpZWxkLCBhbmQgZWFjaCBwYWNrYWdlIGdyZXdcbiAqIGl0cyBvd24gYGZyb21FbnZgIC8gYHJlc29sdmVQb3NpdGl2ZUludGAgaGVscGVyIHdpdGggc2xpZ2h0bHkgZGlmZmVyZW50XG4gKiBjb2VyY2lvbiBydWxlcy4gVGhlc2UgYXJlIHRob3NlIGhlbHBlcnMsIG9uY2UuXG4gKlxuICogQnJvd3Nlci1zYWZlOiBgcHJvY2Vzc2AgaXMgcmVhY2hlZCB0aHJvdWdoIGBnbG9iYWxUaGlzYCBhbmQgZ3VhcmRlZCwgc28gdGhpc1xuICogbW9kdWxlIG5lZWRzIG5vIE5vZGUgdHlwZXMgYW5kIGlzIGluZXJ0IGluIGEgYnJvd3NlciAoZXZlcnkgbG9va3VwIG1pc3NlcyBhbmRcbiAqIHRoZSBjYWxsZXIncyBmYWxsYmFjayBhcHBsaWVzKS5cbiAqXG4gKiBAbW9kdWxlXG4gKi9cblxuaW1wb3J0IHsgdG9Cb29sZWFuIH0gZnJvbSBcIi4vb2JqZWN0LnRzXCI7XG5pbXBvcnQgeyBwYXJzZUxpc3QsIHRyaW1Ub051bGwgfSBmcm9tIFwiLi9zdHJpbmcudHNcIjtcblxuLyoqIGBwcm9jZXNzYC1zaGFwZWQgdmlldyBvZmYgYGdsb2JhbFRoaXNgLCBzbyBubyBub2RlIHR5cGVzIGFyZSBuZWVkZWQuICovXG5pbnRlcmZhY2UgUHJvY2Vzc0xpa2Uge1xuICBlbnY/OiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmcgfCB1bmRlZmluZWQ+O1xufVxuXG4vKiogT25lIGVudiB2YXIgbmFtZSwgb3Igc2V2ZXJhbCB0cmllZCBpbiBvcmRlci4gKi9cbmV4cG9ydCB0eXBlIEVudktleSA9IHN0cmluZyB8IHJlYWRvbmx5IHN0cmluZ1tdO1xuXG4vKiogVGhlIGFtYmllbnQgZW52aXJvbm1lbnQsIG9yIGB7fWAgb2ZmLXByb2Nlc3MgKGEgYnJvd3NlcikuICovXG5mdW5jdGlvbiBlbnZpcm9ubWVudCgpOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmcgfCB1bmRlZmluZWQ+IHtcbiAgcmV0dXJuIChnbG9iYWxUaGlzIGFzIHsgcHJvY2Vzcz86IFByb2Nlc3NMaWtlIH0pLnByb2Nlc3M/LmVudiA/PyB7fTtcbn1cblxuLyoqXG4gKiBGaXJzdCBub24tZW1wdHkgdmFsdWUgYW1vbmcgYGtleXNgLCB0cmltbWVkLCBlbHNlIGBudWxsYC5cbiAqXG4gKiBTZXZlcmFsIG5hbWVzIGZvciBvbmUgc2V0dGluZyBpcyB0aGUgbm9ybSAoYSBwYWNrYWdlLXNwZWNpZmljIHZhcmlhYmxlIHBsdXNcbiAqIHRoZSBEYXRhYnJpY2tzLXN0YW5kYXJkIG9uZSksIHNvIGBrZXlzYCBpcyBvcmRlci1zZW5zaXRpdmU6IGVhcmxpZXIgbmFtZXMgd2luLlxuICpcbiAqIEBleGFtcGxlXG4gKiBlbnYudGV4dChbXCJURUFNU19BUFBfSURcIiwgXCJNSUNST1NPRlRfQVBQX0lEXCJdKTtcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHRleHQoa2V5czogRW52S2V5KTogc3RyaW5nIHwgbnVsbCB7XG4gIGNvbnN0IGVudiA9IGVudmlyb25tZW50KCk7XG4gIGZvciAoY29uc3Qga2V5IG9mIHR5cGVvZiBrZXlzID09PSBcInN0cmluZ1wiID8gW2tleXNdIDoga2V5cykge1xuICAgIGNvbnN0IHZhbHVlID0gdHJpbVRvTnVsbChlbnZba2V5XSk7XG4gICAgaWYgKHZhbHVlICE9PSBudWxsKSByZXR1cm4gdmFsdWU7XG4gIH1cbiAgcmV0dXJuIG51bGw7XG59XG5cbi8qKlxuICogUmVzb2x2ZSBhIHN0cmluZyBzZXR0aW5nOiBgY29uZmlndXJlZGAgd2hlbiBzZXQgYW5kIG5vbi1lbXB0eSwgZWxzZSB0aGUgZmlyc3RcbiAqIG5vbi1lbXB0eSB2YXJpYWJsZSBhbW9uZyBga2V5c2AsIGVsc2UgYG51bGxgLlxuICpcbiAqIEBleGFtcGxlXG4gKiBlbnYuc3RyaW5nKGNvbmZpZy5ob3N0LCBcIlNNVFBfSE9TVFwiKTtcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHN0cmluZyhjb25maWd1cmVkOiB1bmtub3duLCBrZXlzOiBFbnZLZXkpOiBzdHJpbmcgfCBudWxsIHtcbiAgcmV0dXJuIHRyaW1Ub051bGwoY29uZmlndXJlZCkgPz8gdGV4dChrZXlzKTtcbn1cblxuLyoqXG4gKiBSZXNvbHZlIGEgYm9vbGVhbiBzZXR0aW5nIHRocm91Z2gge0BsaW5rIHRvQm9vbGVhbn0sIHNvIHRoZSBsb29zZSBzcGVsbGluZ3MgYW5cbiAqIGVudiB2YXIgYWN0dWFsbHkgY2FycmllcyAoYDFgLCBgb25gLCBgeWVzYCwgLi4uKSBhcmUgYWNjZXB0ZWQuIFJldHVybnNcbiAqIGB1bmRlZmluZWRgIHdoZW4gbmVpdGhlciBzb3VyY2UgaXMgaW50ZXJwcmV0YWJsZSwgbGV0dGluZyB0aGUgY2FsbGVyIHBpY2sgYVxuICogZGVmYXVsdCB3aXRoIGA/P2AuXG4gKlxuICogQGV4YW1wbGVcbiAqIGVudi5ib29sZWFuKGNvbmZpZy5mdXp6eSwgXCJXRUJfU0VBUkNIX0ZVWlpZXCIpID8/IHRydWU7XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBib29sZWFuKGNvbmZpZ3VyZWQ6IHVua25vd24sIGtleXM6IEVudktleSk6IGJvb2xlYW4gfCB1bmRlZmluZWQge1xuICByZXR1cm4gdG9Cb29sZWFuKGNvbmZpZ3VyZWQpID8/IHRvQm9vbGVhbih0ZXh0KGtleXMpKTtcbn1cblxuLyoqXG4gKiBSZXNvbHZlIGEgcG9zaXRpdmUtbnVtYmVyIHNldHRpbmcgdGhhdCBtYXkgYmUgZnJhY3Rpb25hbCAoYSBzY29yZSB0aHJlc2hvbGQsXG4gKiBhIHJhdGlvKTogYGNvbmZpZ3VyZWRgIHdoZW4gaXQgaXMgYSBmaW5pdGUgbnVtYmVyIGdyZWF0ZXIgdGhhbiB6ZXJvLCBlbHNlIHRoZVxuICogZmlyc3QgdmFyaWFibGUgYW1vbmcgYGtleXNgIHRoYXQgcGFyc2VzIHRoYXQgd2F5LCBlbHNlIGBmYWxsYmFja2AuXG4gKlxuICoge0BsaW5rIHBvc2l0aXZlSW50fSBpcyB0aGUgcmlnaHQgY2hvaWNlIGZvciBhIGNvdW50OyB0aGlzIG9uZSBrZWVwcyB0aGVcbiAqIGZyYWN0aW9uLCBzbyBhIGAwLjRgIHRocmVzaG9sZCBkb2VzIG5vdCBmbG9vciB0byBgMGAuXG4gKlxuICogQGV4YW1wbGVcbiAqIGVudi5wb3NpdGl2ZU51bWJlcihjb25maWcuZnV6enlUaHJlc2hvbGQsIFwiU0VBUkNIX0ZVWlpZX1RIUkVTSE9MRFwiLCAwLjQpO1xuICovXG5leHBvcnQgZnVuY3Rpb24gcG9zaXRpdmVOdW1iZXIoY29uZmlndXJlZDogdW5rbm93biwga2V5czogRW52S2V5LCBmYWxsYmFjazogbnVtYmVyKTogbnVtYmVyIHtcbiAgcmV0dXJuIHRvUG9zaXRpdmVOdW1iZXIoY29uZmlndXJlZCkgPz8gdG9Qb3NpdGl2ZU51bWJlcih0ZXh0KGtleXMpKSA/PyBmYWxsYmFjaztcbn1cblxuLyoqXG4gKiBSZXNvbHZlIGEgcG9zaXRpdmUtaW50ZWdlciBzZXR0aW5nIChhIHBvcnQsIHRpbWVvdXQsIHBhZ2Ugc2l6ZSwgb3IgY2FwKTpcbiAqIGBjb25maWd1cmVkYCB3aGVuIGl0IGlzIGEgZmluaXRlIG51bWJlciBncmVhdGVyIHRoYW4gemVybywgZWxzZSB0aGUgZmlyc3RcbiAqIHZhcmlhYmxlIGFtb25nIGBrZXlzYCB0aGF0IHBhcnNlcyB0aGF0IHdheSwgZWxzZSBgZmFsbGJhY2tgLiBGbG9vcmVkLCBzbyBhXG4gKiBmcmFjdGlvbmFsIHZhbHVlIGNhbid0IGxlYWsgaW50byBhIGNvdW50LlxuICpcbiAqIEEgbm9uLW51bWVyaWMgb3Igbm9uLXBvc2l0aXZlIHZhbHVlIGlzIHRyZWF0ZWQgYXMgYWJzZW50IHJhdGhlciB0aGFuIGZhdGFsIC1cbiAqIHRoZXNlIGFyZSBjZWlsaW5ncyBhbmQgdGltZW91dHMgd2hlcmUgYSBzYW5lIGRlZmF1bHQgYmVhdHMgYSBib290IGZhaWx1cmUuXG4gKlxuICogQGV4YW1wbGVcbiAqIGVudi5wb3NpdGl2ZUludChjb25maWcudGltZW91dE1zLCBcIlNFQVJDSF9USU1FT1VUX01TXCIsIDMwXzAwMCk7XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBwb3NpdGl2ZUludChjb25maWd1cmVkOiB1bmtub3duLCBrZXlzOiBFbnZLZXksIGZhbGxiYWNrOiBudW1iZXIpOiBudW1iZXIge1xuICByZXR1cm4gdG9Qb3NpdGl2ZUludChjb25maWd1cmVkKSA/PyB0b1Bvc2l0aXZlSW50KHRleHQoa2V5cykpID8/IGZhbGxiYWNrO1xufVxuXG5mdW5jdGlvbiB0b1Bvc2l0aXZlTnVtYmVyKHZhbHVlOiB1bmtub3duKTogbnVtYmVyIHwgdW5kZWZpbmVkIHtcbiAgaWYgKHZhbHVlID09PSBudWxsIHx8IHZhbHVlID09PSB1bmRlZmluZWQgfHwgdmFsdWUgPT09IFwiXCIpIHJldHVybiB1bmRlZmluZWQ7XG4gIGNvbnN0IHBhcnNlZCA9IE51bWJlcih2YWx1ZSk7XG4gIHJldHVybiBOdW1iZXIuaXNGaW5pdGUocGFyc2VkKSAmJiBwYXJzZWQgPiAwID8gcGFyc2VkIDogdW5kZWZpbmVkO1xufVxuXG5mdW5jdGlvbiB0b1Bvc2l0aXZlSW50KHZhbHVlOiB1bmtub3duKTogbnVtYmVyIHwgdW5kZWZpbmVkIHtcbiAgY29uc3QgcGFyc2VkID0gdG9Qb3NpdGl2ZU51bWJlcih2YWx1ZSk7XG4gIHJldHVybiBwYXJzZWQgPT09IHVuZGVmaW5lZCA/IHVuZGVmaW5lZCA6IE1hdGguZmxvb3IocGFyc2VkKTtcbn1cblxuLyoqXG4gKiBSZXNvbHZlIGEgbGlzdCBzZXR0aW5nIHRocm91Z2gge0BsaW5rIHBhcnNlTGlzdH0sIHNvIGFuIGFycmF5IGZyb20gdHlwZWRcbiAqIGNvbmZpZyBhbmQgYSBgXCJhLCBiIGNcImAgZW52IHN0cmluZyBub3JtYWxpemUgaWRlbnRpY2FsbHkuIFJldHVybnMgYFtdYCB3aGVuXG4gKiBuZWl0aGVyIHNvdXJjZSBoYXMgZW50cmllcy5cbiAqXG4gKiBAZXhhbXBsZVxuICogZW52Lmxpc3QoY29uZmlnLm1vZGVsRmFsbGJhY2tzLCBcIldFQl9TRUFSQ0hfTU9ERUxfRkFMTEJBQ0tTXCIpO1xuICovXG5leHBvcnQgZnVuY3Rpb24gbGlzdChcbiAgY29uZmlndXJlZDogc3RyaW5nIHwgcmVhZG9ubHkgc3RyaW5nW10gfCB1bmRlZmluZWQgfCBudWxsLFxuICBrZXlzOiBFbnZLZXksXG4gIHRyYW5zZm9ybT86IChlbnRyeTogc3RyaW5nKSA9PiBzdHJpbmcsXG4pOiBzdHJpbmdbXSB7XG4gIGNvbnN0IGZyb21Db25maWcgPSBwYXJzZUxpc3QoY29uZmlndXJlZCwgdHJhbnNmb3JtKTtcbiAgcmV0dXJuIGZyb21Db25maWcubGVuZ3RoID4gMCA/IGZyb21Db25maWcgOiBwYXJzZUxpc3QodGV4dChrZXlzKSwgdHJhbnNmb3JtKTtcbn1cbiJdfQ==
@@ -0,0 +1,34 @@
1
+ /** Success/failure shape returned by an interceptor-backed executor. */
2
+ export type ExecutionResult<T> = {
3
+ ok: true;
4
+ data: T;
5
+ } | {
6
+ ok: false;
7
+ status: number;
8
+ message: string;
9
+ };
10
+ /** Function signature shared by AppKit plugin executors. */
11
+ export type Executor<Settings> = <T>(fn: (signal?: AbortSignal) => Promise<T>, settings: Settings) => Promise<ExecutionResult<T>>;
12
+ /** Failure details supplied when an intercepted operation cannot complete. */
13
+ export interface ExecutionFailure {
14
+ operation: string;
15
+ status: number;
16
+ message: string;
17
+ }
18
+ /** Options for running and unwrapping one intercepted operation. */
19
+ export interface RunOptions<T, Settings> {
20
+ operation: string;
21
+ settings: Settings;
22
+ execute: Executor<Settings>;
23
+ fn: (signal?: AbortSignal) => Promise<T>;
24
+ signal?: AbortSignal;
25
+ canceled: () => Error;
26
+ failed: (failure: ExecutionFailure) => Error;
27
+ }
28
+ /**
29
+ * Build the executor used before a plugin installs its interceptor chain.
30
+ * Errors are mapped onto the same result shape as AppKit's `Plugin.execute()`.
31
+ */
32
+ export declare function directExecutor<Settings>(): Executor<Settings>;
33
+ /** Run an operation through an executor, merge cancellation, and unwrap its result. */
34
+ export declare function run<T, Settings>(options: RunOptions<T, Settings>): Promise<T>;
@@ -0,0 +1,42 @@
1
+ /** Generic helpers for AppKit-style intercepted execution. */
2
+ import * as async from "./async.js";
3
+ import * as error from "./error.js";
4
+ import * as object from "./object.js";
5
+ function errorStatus(value) {
6
+ if (!object.isRecord(value))
7
+ return 500;
8
+ const status = value.statusCode;
9
+ return typeof status === "number" && Number.isFinite(status) ? status : 500;
10
+ }
11
+ /**
12
+ * Build the executor used before a plugin installs its interceptor chain.
13
+ * Errors are mapped onto the same result shape as AppKit's `Plugin.execute()`.
14
+ */
15
+ export function directExecutor() {
16
+ return async (fn) => {
17
+ try {
18
+ return { ok: true, data: await fn() };
19
+ }
20
+ catch (cause) {
21
+ return {
22
+ ok: false,
23
+ status: errorStatus(cause),
24
+ message: error.errorMessage(cause),
25
+ };
26
+ }
27
+ };
28
+ }
29
+ /** Run an operation through an executor, merge cancellation, and unwrap its result. */
30
+ export async function run(options) {
31
+ const result = await options.execute((executeSignal) => options.fn(async.combineAbortSignals(executeSignal, options.signal)), options.settings);
32
+ if (result.ok)
33
+ return result.data;
34
+ if (options.signal?.aborted)
35
+ throw options.canceled();
36
+ throw options.failed({
37
+ operation: options.operation,
38
+ status: result.status,
39
+ message: result.message,
40
+ });
41
+ }
42
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXhlY3V0aW9uLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2V4ZWN1dGlvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSw4REFBOEQ7QUFDOUQsT0FBTyxLQUFLLEtBQUssTUFBTSxZQUFZLENBQUM7QUFDcEMsT0FBTyxLQUFLLEtBQUssTUFBTSxZQUFZLENBQUM7QUFDcEMsT0FBTyxLQUFLLE1BQU0sTUFBTSxhQUFhLENBQUM7QUE4QnRDLFNBQVMsV0FBVyxDQUFDLEtBQWM7SUFDakMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDO1FBQUUsT0FBTyxHQUFHLENBQUM7SUFDeEMsTUFBTSxNQUFNLEdBQUcsS0FBSyxDQUFDLFVBQVUsQ0FBQztJQUNoQyxPQUFPLE9BQU8sTUFBTSxLQUFLLFFBQVEsSUFBSSxNQUFNLENBQUMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQztBQUM5RSxDQUFDO0FBRUQ7OztHQUdHO0FBQ0gsTUFBTSxVQUFVLGNBQWM7SUFDNUIsT0FBTyxLQUFLLEVBQUUsRUFBRSxFQUFFLEVBQUU7UUFDbEIsSUFBSSxDQUFDO1lBQ0gsT0FBTyxFQUFFLEVBQUUsRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLE1BQU0sRUFBRSxFQUFFLEVBQUUsQ0FBQztRQUN4QyxDQUFDO1FBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztZQUNmLE9BQU87Z0JBQ0wsRUFBRSxFQUFFLEtBQUs7Z0JBQ1QsTUFBTSxFQUFFLFdBQVcsQ0FBQyxLQUFLLENBQUM7Z0JBQzFCLE9BQU8sRUFBRSxLQUFLLENBQUMsWUFBWSxDQUFDLEtBQUssQ0FBQzthQUNuQyxDQUFDO1FBQ0osQ0FBQztJQUNILENBQUMsQ0FBQztBQUNKLENBQUM7QUFFRCx1RkFBdUY7QUFDdkYsTUFBTSxDQUFDLEtBQUssVUFBVSxHQUFHLENBQWMsT0FBZ0M7SUFDckUsTUFBTSxNQUFNLEdBQUcsTUFBTSxPQUFPLENBQUMsT0FBTyxDQUNsQyxDQUFDLGFBQWEsRUFBRSxFQUFFLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQyxLQUFLLENBQUMsbUJBQW1CLENBQUMsYUFBYSxFQUFFLE9BQU8sQ0FBQyxNQUFNLENBQUMsQ0FBQyxFQUN2RixPQUFPLENBQUMsUUFBUSxDQUNqQixDQUFDO0lBQ0YsSUFBSSxNQUFNLENBQUMsRUFBRTtRQUFFLE9BQU8sTUFBTSxDQUFDLElBQUksQ0FBQztJQUNsQyxJQUFJLE9BQU8sQ0FBQyxNQUFNLEVBQUUsT0FBTztRQUFFLE1BQU0sT0FBTyxDQUFDLFFBQVEsRUFBRSxDQUFDO0lBQ3RELE1BQU0sT0FBTyxDQUFDLE1BQU0sQ0FBQztRQUNuQixTQUFTLEVBQUUsT0FBTyxDQUFDLFNBQVM7UUFDNUIsTUFBTSxFQUFFLE1BQU0sQ0FBQyxNQUFNO1FBQ3JCLE9BQU8sRUFBRSxNQUFNLENBQUMsT0FBTztLQUN4QixDQUFDLENBQUM7QUFDTCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqIEdlbmVyaWMgaGVscGVycyBmb3IgQXBwS2l0LXN0eWxlIGludGVyY2VwdGVkIGV4ZWN1dGlvbi4gKi9cbmltcG9ydCAqIGFzIGFzeW5jIGZyb20gXCIuL2FzeW5jLnRzXCI7XG5pbXBvcnQgKiBhcyBlcnJvciBmcm9tIFwiLi9lcnJvci50c1wiO1xuaW1wb3J0ICogYXMgb2JqZWN0IGZyb20gXCIuL29iamVjdC50c1wiO1xuXG4vKiogU3VjY2Vzcy9mYWlsdXJlIHNoYXBlIHJldHVybmVkIGJ5IGFuIGludGVyY2VwdG9yLWJhY2tlZCBleGVjdXRvci4gKi9cbmV4cG9ydCB0eXBlIEV4ZWN1dGlvblJlc3VsdDxUPiA9XG4gIHsgb2s6IHRydWU7IGRhdGE6IFQgfSB8IHsgb2s6IGZhbHNlOyBzdGF0dXM6IG51bWJlcjsgbWVzc2FnZTogc3RyaW5nIH07XG5cbi8qKiBGdW5jdGlvbiBzaWduYXR1cmUgc2hhcmVkIGJ5IEFwcEtpdCBwbHVnaW4gZXhlY3V0b3JzLiAqL1xuZXhwb3J0IHR5cGUgRXhlY3V0b3I8U2V0dGluZ3M+ID0gPFQ+KFxuICBmbjogKHNpZ25hbD86IEFib3J0U2lnbmFsKSA9PiBQcm9taXNlPFQ+LFxuICBzZXR0aW5nczogU2V0dGluZ3MsXG4pID0+IFByb21pc2U8RXhlY3V0aW9uUmVzdWx0PFQ+PjtcblxuLyoqIEZhaWx1cmUgZGV0YWlscyBzdXBwbGllZCB3aGVuIGFuIGludGVyY2VwdGVkIG9wZXJhdGlvbiBjYW5ub3QgY29tcGxldGUuICovXG5leHBvcnQgaW50ZXJmYWNlIEV4ZWN1dGlvbkZhaWx1cmUge1xuICBvcGVyYXRpb246IHN0cmluZztcbiAgc3RhdHVzOiBudW1iZXI7XG4gIG1lc3NhZ2U6IHN0cmluZztcbn1cblxuLyoqIE9wdGlvbnMgZm9yIHJ1bm5pbmcgYW5kIHVud3JhcHBpbmcgb25lIGludGVyY2VwdGVkIG9wZXJhdGlvbi4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgUnVuT3B0aW9uczxULCBTZXR0aW5ncz4ge1xuICBvcGVyYXRpb246IHN0cmluZztcbiAgc2V0dGluZ3M6IFNldHRpbmdzO1xuICBleGVjdXRlOiBFeGVjdXRvcjxTZXR0aW5ncz47XG4gIGZuOiAoc2lnbmFsPzogQWJvcnRTaWduYWwpID0+IFByb21pc2U8VD47XG4gIHNpZ25hbD86IEFib3J0U2lnbmFsO1xuICBjYW5jZWxlZDogKCkgPT4gRXJyb3I7XG4gIGZhaWxlZDogKGZhaWx1cmU6IEV4ZWN1dGlvbkZhaWx1cmUpID0+IEVycm9yO1xufVxuXG5mdW5jdGlvbiBlcnJvclN0YXR1cyh2YWx1ZTogdW5rbm93bik6IG51bWJlciB7XG4gIGlmICghb2JqZWN0LmlzUmVjb3JkKHZhbHVlKSkgcmV0dXJuIDUwMDtcbiAgY29uc3Qgc3RhdHVzID0gdmFsdWUuc3RhdHVzQ29kZTtcbiAgcmV0dXJuIHR5cGVvZiBzdGF0dXMgPT09IFwibnVtYmVyXCIgJiYgTnVtYmVyLmlzRmluaXRlKHN0YXR1cykgPyBzdGF0dXMgOiA1MDA7XG59XG5cbi8qKlxuICogQnVpbGQgdGhlIGV4ZWN1dG9yIHVzZWQgYmVmb3JlIGEgcGx1Z2luIGluc3RhbGxzIGl0cyBpbnRlcmNlcHRvciBjaGFpbi5cbiAqIEVycm9ycyBhcmUgbWFwcGVkIG9udG8gdGhlIHNhbWUgcmVzdWx0IHNoYXBlIGFzIEFwcEtpdCdzIGBQbHVnaW4uZXhlY3V0ZSgpYC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGRpcmVjdEV4ZWN1dG9yPFNldHRpbmdzPigpOiBFeGVjdXRvcjxTZXR0aW5ncz4ge1xuICByZXR1cm4gYXN5bmMgKGZuKSA9PiB7XG4gICAgdHJ5IHtcbiAgICAgIHJldHVybiB7IG9rOiB0cnVlLCBkYXRhOiBhd2FpdCBmbigpIH07XG4gICAgfSBjYXRjaCAoY2F1c2UpIHtcbiAgICAgIHJldHVybiB7XG4gICAgICAgIG9rOiBmYWxzZSxcbiAgICAgICAgc3RhdHVzOiBlcnJvclN0YXR1cyhjYXVzZSksXG4gICAgICAgIG1lc3NhZ2U6IGVycm9yLmVycm9yTWVzc2FnZShjYXVzZSksXG4gICAgICB9O1xuICAgIH1cbiAgfTtcbn1cblxuLyoqIFJ1biBhbiBvcGVyYXRpb24gdGhyb3VnaCBhbiBleGVjdXRvciwgbWVyZ2UgY2FuY2VsbGF0aW9uLCBhbmQgdW53cmFwIGl0cyByZXN1bHQuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gcnVuPFQsIFNldHRpbmdzPihvcHRpb25zOiBSdW5PcHRpb25zPFQsIFNldHRpbmdzPik6IFByb21pc2U8VD4ge1xuICBjb25zdCByZXN1bHQgPSBhd2FpdCBvcHRpb25zLmV4ZWN1dGUoXG4gICAgKGV4ZWN1dGVTaWduYWwpID0+IG9wdGlvbnMuZm4oYXN5bmMuY29tYmluZUFib3J0U2lnbmFscyhleGVjdXRlU2lnbmFsLCBvcHRpb25zLnNpZ25hbCkpLFxuICAgIG9wdGlvbnMuc2V0dGluZ3MsXG4gICk7XG4gIGlmIChyZXN1bHQub2spIHJldHVybiByZXN1bHQuZGF0YTtcbiAgaWYgKG9wdGlvbnMuc2lnbmFsPy5hYm9ydGVkKSB0aHJvdyBvcHRpb25zLmNhbmNlbGVkKCk7XG4gIHRocm93IG9wdGlvbnMuZmFpbGVkKHtcbiAgICBvcGVyYXRpb246IG9wdGlvbnMub3BlcmF0aW9uLFxuICAgIHN0YXR1czogcmVzdWx0LnN0YXR1cyxcbiAgICBtZXNzYWdlOiByZXN1bHQubWVzc2FnZSxcbiAgfSk7XG59XG4iXX0=
@@ -3,8 +3,9 @@
3
3
  *
4
4
  * Value guards / coercions / shape types: {@link isRecord} narrows parsed JSON
5
5
  * to a record, {@link toBoolean} coerces loose truthy/falsy values, {@link
6
- * deepEqual} compares structurally, and {@link NameLike}/{@link NonFunctionKeys}
7
- * describe object shapes.
6
+ * optional} spreads a field only when it is present, {@link deepEqual} compares
7
+ * structurally, and {@link NameLike}/{@link NonFunctionKeys} describe object
8
+ * shapes.
8
9
  *
9
10
  * Iterable helpers: {@link generator} flattens mixed arguments; {@link sequence}
10
11
  * wraps source(s) in a lazy, `Array`-compatible {@link Sequence}. Every
@@ -349,6 +350,22 @@ export type NonFunctionKeys<T> = {
349
350
  * if (isRecord(parsed)) parsed.foo = 1;
350
351
  */
351
352
  export declare function isRecord(value: unknown): value is Record<string, unknown>;
353
+ /**
354
+ * `{ [key]: value }` when `value` is present, otherwise `undefined` - so an
355
+ * absent optional field stays ABSENT when spread, rather than becoming an
356
+ * explicit `undefined` that `exactOptionalPropertyTypes` rejects.
357
+ *
358
+ * Spelling that inline costs a repeated, double-evaluated ternary per field
359
+ * (`...(v ? { key: v } : {})`), which is how config resolvers end up computing
360
+ * the same value twice.
361
+ *
362
+ * @example
363
+ * return {
364
+ * ...optional("appId", env.text("MICROSOFT_APP_ID")),
365
+ * ...optional("endpoint", config.endpoint),
366
+ * };
367
+ */
368
+ export declare function optional<K extends string, V>(key: K, value: V | null | undefined): Record<K, V> | undefined;
352
369
  /**
353
370
  * Coerce a loose boolean-ish value to a real `boolean`, or `undefined`
354
371
  * when it can't be interpreted. Recognizes `true`/`t`/`on`/`1`/`yes`/`y`