@microsoft/agents-hosting 1.9.0-beta.3.gb2d23c1c44 → 1.9.1
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 +182 -4
- package/dist/package.json +3 -3
- package/dist/src/app/agentApplication.d.ts +4 -3
- package/dist/src/app/agentApplication.js +24 -21
- package/dist/src/app/agentApplication.js.map +1 -1
- package/dist/src/app/agentApplicationBuilder.d.ts +9 -2
- package/dist/src/app/agentApplicationBuilder.js +9 -0
- package/dist/src/app/agentApplicationBuilder.js.map +1 -1
- package/dist/src/app/agentApplicationOptions.d.ts +16 -4
- package/dist/src/app/auth/authorization.d.ts +54 -0
- package/dist/src/app/auth/authorization.js +63 -2
- package/dist/src/app/auth/authorization.js.map +1 -1
- package/dist/src/app/auth/authorizationManager.d.ts +0 -8
- package/dist/src/app/auth/authorizationManager.js +104 -178
- package/dist/src/app/auth/authorizationManager.js.map +1 -1
- package/dist/src/app/auth/handlerStorage.d.ts +5 -3
- package/dist/src/app/auth/handlerStorage.js +36 -10
- package/dist/src/app/auth/handlerStorage.js.map +1 -1
- package/dist/src/app/auth/types.d.ts +2 -2
- package/dist/src/app/index.d.ts +1 -0
- package/dist/src/app/proactive/proactive.js +10 -6
- package/dist/src/app/proactive/proactive.js.map +1 -1
- package/dist/src/app/proactive/proactiveOptions.d.ts +2 -2
- package/dist/src/app/rateLimit.d.ts +315 -0
- package/dist/src/app/rateLimit.js +333 -0
- package/dist/src/app/rateLimit.js.map +1 -0
- package/dist/src/app/turnState.d.ts +6 -3
- package/dist/src/app/turnState.js +43 -4
- package/dist/src/app/turnState.js.map +1 -1
- package/dist/src/auth/authConfiguration.d.ts +10 -3
- package/dist/src/auth/authConfiguration.js +292 -259
- package/dist/src/auth/authConfiguration.js.map +1 -1
- package/dist/src/auth/delegatedTokenCredential.d.ts +29 -0
- package/dist/src/auth/delegatedTokenCredential.js +49 -0
- package/dist/src/auth/delegatedTokenCredential.js.map +1 -0
- package/dist/src/auth/msal/msalTokenCredential.js +1 -1
- package/dist/src/auth/msal/msalTokenCredential.js.map +1 -1
- package/dist/src/auth/settings.d.ts +8 -70
- package/dist/src/auth/settings.js +5 -67
- package/dist/src/auth/settings.js.map +1 -1
- package/dist/src/cloudAdapter.d.ts +5 -0
- package/dist/src/cloudAdapter.js +13 -103
- package/dist/src/cloudAdapter.js.map +1 -1
- package/dist/src/configuration/botFrameworkEnvironmentCompatibility.d.ts +23 -0
- package/dist/src/configuration/botFrameworkEnvironmentCompatibility.js +183 -0
- package/dist/src/configuration/botFrameworkEnvironmentCompatibility.js.map +1 -0
- package/dist/src/configuration/configuration.d.ts +83 -0
- package/dist/src/configuration/configuration.js +836 -0
- package/dist/src/configuration/configuration.js.map +1 -0
- package/dist/src/configuration/configurationSource.d.ts +57 -0
- package/dist/src/configuration/configurationSource.js +7 -0
- package/dist/src/configuration/configurationSource.js.map +1 -0
- package/dist/src/configuration/environmentConfiguration.d.ts +14 -0
- package/dist/src/configuration/environmentConfiguration.js +271 -0
- package/dist/src/configuration/environmentConfiguration.js.map +1 -0
- package/dist/src/configuration/index.d.ts +7 -0
- package/dist/src/configuration/index.js +14 -0
- package/dist/src/configuration/index.js.map +1 -0
- package/dist/src/configuration/jsonFileConfigurationSource.d.ts +15 -0
- package/dist/src/configuration/jsonFileConfigurationSource.js +58 -0
- package/dist/src/configuration/jsonFileConfigurationSource.js.map +1 -0
- package/dist/src/createCloudAdapter.d.ts +30 -5
- package/dist/src/createCloudAdapter.js +17 -10
- package/dist/src/createCloudAdapter.js.map +1 -1
- package/dist/src/errorHelper.js +124 -5
- package/dist/src/errorHelper.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/observability/traces.d.ts +6 -4
- package/dist/src/observability/traces.js +10 -9
- package/dist/src/observability/traces.js.map +1 -1
- package/dist/src/outboundHostValidator.d.ts +4 -1
- package/dist/src/outboundHostValidator.js +25 -48
- package/dist/src/outboundHostValidator.js.map +1 -1
- package/dist/src/state/agentState.d.ts +8 -3
- package/dist/src/state/agentState.js +26 -8
- package/dist/src/state/agentState.js.map +1 -1
- package/dist/src/state/conversationState.d.ts +2 -2
- package/dist/src/state/conversationState.js.map +1 -1
- package/dist/src/state/userState.d.ts +2 -2
- package/dist/src/state/userState.js.map +1 -1
- package/dist/src/statusCodes.d.ts +5 -0
- package/dist/src/statusCodes.js +5 -0
- package/dist/src/statusCodes.js.map +1 -1
- package/dist/src/storage/fileStorage.d.ts +83 -68
- package/dist/src/storage/fileStorage.js +279 -105
- package/dist/src/storage/fileStorage.js.map +1 -1
- package/dist/src/storage/index.d.ts +1 -0
- package/dist/src/storage/index.js +1 -0
- package/dist/src/storage/index.js.map +1 -1
- package/dist/src/storage/memoryStorage.d.ts +85 -60
- package/dist/src/storage/memoryStorage.js +244 -87
- package/dist/src/storage/memoryStorage.js.map +1 -1
- package/dist/src/storage/storage.d.ts +100 -2
- package/dist/src/storage/storage.js +25 -0
- package/dist/src/storage/storage.js.map +1 -1
- package/dist/src/storage/storageCompatibility.d.ts +17 -0
- package/dist/src/storage/storageCompatibility.js +157 -0
- package/dist/src/storage/storageCompatibility.js.map +1 -0
- package/dist/src/storage/storageExpiry.d.ts +6 -0
- package/dist/src/storage/storageExpiry.js +20 -0
- package/dist/src/storage/storageExpiry.js.map +1 -0
- package/dist/src/utils/env.d.ts +43 -0
- package/dist/src/utils/env.js +42 -6
- package/dist/src/utils/env.js.map +1 -1
- package/dist/src/utils.d.ts +1 -0
- package/dist/src/utils.js +10 -1
- package/dist/src/utils.js.map +1 -1
- package/package.json +3 -3
- package/src/app/agentApplication.ts +27 -14
- package/src/app/agentApplicationBuilder.ts +13 -2
- package/src/app/agentApplicationOptions.ts +18 -4
- package/src/app/auth/authorization.ts +67 -2
- package/src/app/auth/authorizationManager.ts +126 -204
- package/src/app/auth/handlerStorage.ts +51 -7
- package/src/app/auth/types.ts +2 -2
- package/src/app/index.ts +7 -0
- package/src/app/proactive/proactive.ts +19 -9
- package/src/app/proactive/proactiveOptions.ts +2 -2
- package/src/app/rateLimit.ts +659 -0
- package/src/app/turnState.ts +50 -7
- package/src/auth/authConfiguration.ts +415 -260
- package/src/auth/delegatedTokenCredential.ts +49 -0
- package/src/auth/msal/msalTokenCredential.ts +1 -1
- package/src/auth/settings.ts +5 -82
- package/src/cloudAdapter.ts +38 -113
- package/src/configuration/botFrameworkEnvironmentCompatibility.ts +245 -0
- package/src/configuration/configuration.ts +1076 -0
- package/src/configuration/configurationSource.ts +76 -0
- package/src/configuration/environmentConfiguration.ts +377 -0
- package/src/configuration/index.ts +19 -0
- package/src/configuration/jsonFileConfigurationSource.ts +74 -0
- package/src/createCloudAdapter.ts +52 -9
- package/src/errorHelper.ts +151 -5
- package/src/index.ts +1 -0
- package/src/observability/traces.ts +8 -7
- package/src/outboundHostValidator.ts +37 -44
- package/src/state/agentState.ts +39 -13
- package/src/state/conversationState.ts +2 -2
- package/src/state/userState.ts +2 -2
- package/src/statusCodes.ts +6 -0
- package/src/storage/fileStorage.ts +299 -106
- package/src/storage/index.ts +1 -0
- package/src/storage/memoryStorage.ts +268 -85
- package/src/storage/storage.ts +114 -2
- package/src/storage/storageCompatibility.ts +185 -0
- package/src/storage/storageExpiry.ts +21 -0
- package/src/utils/env.ts +63 -0
- package/src/utils.ts +10 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ExceptionHelper } from '@microsoft/agents-activity'
|
|
7
|
+
import { Errors } from '../errorHelper'
|
|
8
|
+
import {
|
|
9
|
+
Storage,
|
|
10
|
+
StorageDeleteOptions,
|
|
11
|
+
StorageDeleteResults,
|
|
12
|
+
StorageOperationStatus,
|
|
13
|
+
StorageProvider,
|
|
14
|
+
StorageReadResults,
|
|
15
|
+
StorageV2,
|
|
16
|
+
StorageWriteMode,
|
|
17
|
+
StorageWriteOptions,
|
|
18
|
+
StorageWriteResults,
|
|
19
|
+
} from './storage'
|
|
20
|
+
|
|
21
|
+
/** Returns true when a storage implementation declares the V2 contract. */
|
|
22
|
+
export function isStorageV2 (storage: StorageProvider): storage is StorageV2 {
|
|
23
|
+
return storage instanceof StorageV2
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Converts a supported storage implementation to the V2 contract. */
|
|
27
|
+
export function asStorageV2 (storage: StorageProvider): StorageV2 {
|
|
28
|
+
return isStorageV2(storage) ? storage : new StorageToStorageV2Adapter(storage)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Returns a successful V2 read value, maps not-found to undefined, and rejects invalid results. */
|
|
32
|
+
export function getStorageReadValue<T extends object> (results: StorageReadResults<T> | null | undefined, key: string): T | undefined {
|
|
33
|
+
const result = results?.[key]
|
|
34
|
+
if (result?.status === StorageOperationStatus.NotFound) return undefined
|
|
35
|
+
if (result?.status === StorageOperationStatus.Succeeded) return result.value
|
|
36
|
+
throwStorageResultError('read', key, result?.status)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Adapts the legacy storage contract to version 2 where its semantics allow it. */
|
|
40
|
+
class StorageToStorageV2Adapter extends StorageV2 {
|
|
41
|
+
constructor (private readonly storage: Storage) {
|
|
42
|
+
super()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async read<T extends object = Record<string, unknown>> (keys: string[]): Promise<StorageReadResults<T>> {
|
|
46
|
+
validateKeys(keys)
|
|
47
|
+
if (keys.length === 0) return {}
|
|
48
|
+
|
|
49
|
+
const items = await this.storage.read(keys)
|
|
50
|
+
return Object.fromEntries(keys.map(key => {
|
|
51
|
+
const value = items[key]
|
|
52
|
+
return [key, Object.prototype.hasOwnProperty.call(items, key)
|
|
53
|
+
? { key, status: StorageOperationStatus.Succeeded, value: value as T, version: value?.eTag as string | undefined }
|
|
54
|
+
: { key, status: StorageOperationStatus.NotFound }]
|
|
55
|
+
}))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async write<T extends object = Record<string, unknown>> (changes: Record<string, T>, options?: StorageWriteOptions): Promise<StorageWriteResults> {
|
|
59
|
+
validateChanges(changes)
|
|
60
|
+
if (options?.mode !== undefined && options.mode !== StorageWriteMode.Upsert) {
|
|
61
|
+
throwUnsupportedOption('mode')
|
|
62
|
+
}
|
|
63
|
+
if (options?.ttl !== undefined) {
|
|
64
|
+
throwUnsupportedOption('ttl')
|
|
65
|
+
}
|
|
66
|
+
validateExpectedVersion(options?.expectedVersion)
|
|
67
|
+
if (Object.keys(changes).length === 0) return {}
|
|
68
|
+
const legacyChanges = Object.fromEntries(Object.entries(changes).map(([key, value]) => [
|
|
69
|
+
key,
|
|
70
|
+
options?.expectedVersion === undefined
|
|
71
|
+
? removeLegacyETag(value)
|
|
72
|
+
: { ...removeLegacyETag(value), eTag: options.expectedVersion },
|
|
73
|
+
]))
|
|
74
|
+
await this.storage.write(legacyChanges)
|
|
75
|
+
return Object.fromEntries(Object.keys(changes).map(key => [key, { key, status: StorageOperationStatus.Succeeded }]))
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async delete (keys: string[], options?: StorageDeleteOptions): Promise<StorageDeleteResults> {
|
|
79
|
+
validateDeleteKeys(keys)
|
|
80
|
+
validateExpectedVersion(options?.expectedVersion)
|
|
81
|
+
if (options?.expectedVersion !== undefined) {
|
|
82
|
+
throwUnsupportedOption('expectedVersion')
|
|
83
|
+
}
|
|
84
|
+
if (keys.length === 0) return {}
|
|
85
|
+
|
|
86
|
+
await this.storage.delete(keys)
|
|
87
|
+
return Object.fromEntries(keys.map(key => [key, { key, status: StorageOperationStatus.Succeeded }]))
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function validateKeys (keys: string[]): void {
|
|
92
|
+
if (!Array.isArray(keys)) {
|
|
93
|
+
throw ExceptionHelper.generateException(ReferenceError, Errors.StorageReadKeysRequired)
|
|
94
|
+
}
|
|
95
|
+
if (keys.some(key => typeof key !== 'string' || key.trim() === '')) {
|
|
96
|
+
throw ExceptionHelper.generateException(ReferenceError, Errors.StorageV2KeyRequired)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function validateDeleteKeys (keys: string[]): void {
|
|
101
|
+
if (!Array.isArray(keys)) {
|
|
102
|
+
throw ExceptionHelper.generateException(ReferenceError, Errors.StorageDeleteKeysRequired)
|
|
103
|
+
}
|
|
104
|
+
if (keys.some(key => typeof key !== 'string' || key.trim() === '')) {
|
|
105
|
+
throw ExceptionHelper.generateException(ReferenceError, Errors.StorageV2KeyRequired)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function validateChanges (changes: Record<string, unknown>): void {
|
|
110
|
+
if (changes === null || typeof changes !== 'object' || Array.isArray(changes)) {
|
|
111
|
+
throw ExceptionHelper.generateException(ReferenceError, Errors.StorageWriteChangesRequired)
|
|
112
|
+
}
|
|
113
|
+
if (Object.keys(changes).some(key => key.trim() === '')) {
|
|
114
|
+
throw ExceptionHelper.generateException(ReferenceError, Errors.StorageV2KeyRequired)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function removeLegacyETag<T> (value: T): T {
|
|
119
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) return value
|
|
120
|
+
const { eTag: _eTag, ...withoutETag } = value as Record<string, unknown>
|
|
121
|
+
return withoutETag as T
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function validateExpectedVersion (expectedVersion: string | undefined): void {
|
|
125
|
+
if (expectedVersion === '') {
|
|
126
|
+
throw ExceptionHelper.generateException(RangeError, Errors.StorageV2ExpectedVersionEmpty)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function throwUnsupportedOption (option: string): never {
|
|
131
|
+
throw ExceptionHelper.generateException(Error, Errors.StorageV2UnsupportedOption, undefined, { option })
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Throws when a V2 write did not succeed for every requested key. */
|
|
135
|
+
export function assertStorageWriteSucceeded (results: StorageWriteResults | null | undefined, keys: string[]): void {
|
|
136
|
+
assertStorageResults('write', results, keys, new Set([StorageOperationStatus.Succeeded]))
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Throws the state-specific concurrency error when a state write does not succeed. */
|
|
140
|
+
export function assertAgentStateWriteSucceeded (
|
|
141
|
+
results: StorageWriteResults | null | undefined,
|
|
142
|
+
key: string,
|
|
143
|
+
name: string
|
|
144
|
+
): void {
|
|
145
|
+
const status = results?.[key]?.status
|
|
146
|
+
if (status !== StorageOperationStatus.Succeeded) {
|
|
147
|
+
throw ExceptionHelper.generateException(Error, Errors.AgentStateWriteConflict, undefined, {
|
|
148
|
+
name,
|
|
149
|
+
key,
|
|
150
|
+
status: status ?? 'missing',
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Throws when a V2 delete did not complete with idempotent V1 semantics. */
|
|
156
|
+
export function assertStorageDeleteSucceeded (results: StorageDeleteResults | null | undefined, keys: string[]): void {
|
|
157
|
+
assertStorageResults(
|
|
158
|
+
'delete',
|
|
159
|
+
results,
|
|
160
|
+
keys,
|
|
161
|
+
new Set([StorageOperationStatus.Succeeded, StorageOperationStatus.NotFound])
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function assertStorageResults (
|
|
166
|
+
operation: 'write' | 'delete',
|
|
167
|
+
results: StorageWriteResults | StorageDeleteResults | null | undefined,
|
|
168
|
+
keys: string[],
|
|
169
|
+
acceptedStatuses: ReadonlySet<StorageOperationStatus>
|
|
170
|
+
): void {
|
|
171
|
+
for (const key of keys) {
|
|
172
|
+
const status = results?.[key]?.status
|
|
173
|
+
if (status === undefined || !acceptedStatuses.has(status)) {
|
|
174
|
+
throwStorageResultError(operation, key, status)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function throwStorageResultError (operation: 'read' | 'write' | 'delete', key: string, status?: StorageOperationStatus): never {
|
|
180
|
+
throw ExceptionHelper.generateException(Error, Errors.StorageV2OperationFailed, undefined, {
|
|
181
|
+
operation,
|
|
182
|
+
key,
|
|
183
|
+
status: status ?? 'missing',
|
|
184
|
+
})
|
|
185
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { StorageWriteOptions } from './storage'
|
|
7
|
+
import { ExceptionHelper } from '@microsoft/agents-activity'
|
|
8
|
+
import { Errors } from '../errorHelper'
|
|
9
|
+
|
|
10
|
+
export function getStorageWriteExpiry (options?: StorageWriteOptions): number | undefined {
|
|
11
|
+
const ttl = options?.ttl
|
|
12
|
+
if (ttl === undefined) {
|
|
13
|
+
return undefined
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (!Number.isFinite(ttl) || ttl <= 0) {
|
|
17
|
+
throw ExceptionHelper.generateException(RangeError, Errors.InvalidStorageTtl)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return Date.now() + ttl * 1000
|
|
21
|
+
}
|
package/src/utils/env.ts
CHANGED
|
@@ -10,6 +10,69 @@
|
|
|
10
10
|
* normalize behavior across the package without growing the public API.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
export interface LoadEnv {
|
|
14
|
+
[upperKey: string]: { key: string, value: any }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type ParserSettings<K extends string> = {
|
|
18
|
+
[key in K]: (value: string) => { key?: string, value?: any } | undefined
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Indexes non-empty environment values case-insensitively while preserving
|
|
23
|
+
* their original key spelling.
|
|
24
|
+
*/
|
|
25
|
+
export function loadEnvSettings (
|
|
26
|
+
callback: (key: string, value: string) => void,
|
|
27
|
+
environment: NodeJS.ProcessEnv = process.env
|
|
28
|
+
): LoadEnv {
|
|
29
|
+
const env: LoadEnv = {}
|
|
30
|
+
for (const [envKey, envValue] of Object.entries(environment)) {
|
|
31
|
+
if (!envValue?.trim()) {
|
|
32
|
+
continue
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
env[envKey.toUpperCase()] = { key: envKey, value: envValue }
|
|
36
|
+
callback(envKey, envValue)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return env
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Creates a case-insensitive parser for a fixed set of environment keys.
|
|
44
|
+
*/
|
|
45
|
+
export function envParser<K extends string> (
|
|
46
|
+
settings: ParserSettings<K> & ThisType<ParserSettings<K>>
|
|
47
|
+
) {
|
|
48
|
+
const keys = Object.keys(settings) as K[]
|
|
49
|
+
const upperKeys = keys.reduce((acc, key) => {
|
|
50
|
+
acc[key.toUpperCase()] = key
|
|
51
|
+
return acc
|
|
52
|
+
}, {} as Record<string, K>)
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
keys,
|
|
56
|
+
parse (key: K, value: string) {
|
|
57
|
+
const match = upperKeys[key.toUpperCase()]
|
|
58
|
+
if (!match) {
|
|
59
|
+
return {}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const result = settings[match](value)
|
|
63
|
+
return { key: result?.key ?? match, value: result?.value }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const envParserUtils = {
|
|
69
|
+
bypass: (value: string) => ({ value }),
|
|
70
|
+
redirect: <Parser extends ReturnType<typeof envParser>>(
|
|
71
|
+
parser: Parser,
|
|
72
|
+
key: Parameters<Parser['parse']>[0]
|
|
73
|
+
) => (value: string) => parser.parse(key, value)
|
|
74
|
+
}
|
|
75
|
+
|
|
13
76
|
/**
|
|
14
77
|
* Parses an environment-variable string into a boolean.
|
|
15
78
|
*
|
package/src/utils.ts
CHANGED
|
@@ -12,3 +12,13 @@ export const prune = <T extends Record<string, any>>(obj: T) => {
|
|
|
12
12
|
const entries = Object.entries(obj).filter(([, value]) => value !== undefined)
|
|
13
13
|
return Object.fromEntries(entries) as T
|
|
14
14
|
}
|
|
15
|
+
|
|
16
|
+
export const mergeDefined = <T extends Record<string, any>>(
|
|
17
|
+
...layers: readonly (Partial<T> | undefined)[]
|
|
18
|
+
): T => {
|
|
19
|
+
const result: Partial<T> = {}
|
|
20
|
+
for (const layer of layers) {
|
|
21
|
+
if (layer) Object.assign(result, prune(layer))
|
|
22
|
+
}
|
|
23
|
+
return result as T
|
|
24
|
+
}
|