@microsoft/agents-hosting 1.1.0-alpha.2 → 1.1.0-alpha.58

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.
Files changed (150) hide show
  1. package/dist/package.json +10 -6
  2. package/dist/src/activityWireCompat.js +8 -3
  3. package/dist/src/activityWireCompat.js.map +1 -1
  4. package/dist/src/agent-client/agentClient.js +7 -3
  5. package/dist/src/agent-client/agentClient.js.map +1 -1
  6. package/dist/src/agent-client/agentResponseHandler.js +6 -2
  7. package/dist/src/agent-client/agentResponseHandler.js.map +1 -1
  8. package/dist/src/app/agentApplication.d.ts +26 -11
  9. package/dist/src/app/agentApplication.js +94 -86
  10. package/dist/src/app/agentApplication.js.map +1 -1
  11. package/dist/src/app/agentApplicationBuilder.d.ts +2 -2
  12. package/dist/src/app/agentApplicationBuilder.js.map +1 -1
  13. package/dist/src/app/agentApplicationOptions.d.ts +9 -2
  14. package/dist/src/app/appRoute.d.ts +7 -0
  15. package/dist/src/app/attachmentDownloader.d.ts +13 -3
  16. package/dist/src/app/attachmentDownloader.js +16 -3
  17. package/dist/src/app/attachmentDownloader.js.map +1 -1
  18. package/dist/src/app/{authorization.d.ts → auth/authorization.d.ts} +33 -139
  19. package/dist/src/app/auth/authorization.js +188 -0
  20. package/dist/src/app/auth/authorization.js.map +1 -0
  21. package/dist/src/app/auth/authorizationManager.d.ts +71 -0
  22. package/dist/src/app/auth/authorizationManager.js +170 -0
  23. package/dist/src/app/auth/authorizationManager.js.map +1 -0
  24. package/dist/src/app/auth/handlerStorage.d.ts +36 -0
  25. package/dist/src/app/auth/handlerStorage.js +62 -0
  26. package/dist/src/app/auth/handlerStorage.js.map +1 -0
  27. package/dist/src/app/auth/handlers/agenticAuthorization.d.ts +97 -0
  28. package/dist/src/app/auth/handlers/agenticAuthorization.js +145 -0
  29. package/dist/src/app/auth/handlers/agenticAuthorization.js.map +1 -0
  30. package/dist/src/app/auth/handlers/azureBotAuthorization.d.ts +222 -0
  31. package/dist/src/app/auth/handlers/azureBotAuthorization.js +428 -0
  32. package/dist/src/app/auth/handlers/azureBotAuthorization.js.map +1 -0
  33. package/dist/src/app/auth/handlers/index.d.ts +2 -0
  34. package/dist/src/app/auth/handlers/index.js +19 -0
  35. package/dist/src/app/auth/handlers/index.js.map +1 -0
  36. package/dist/src/app/auth/index.d.ts +2 -0
  37. package/dist/src/app/auth/index.js +19 -0
  38. package/dist/src/app/auth/index.js.map +1 -0
  39. package/dist/src/app/auth/types.d.ts +104 -0
  40. package/dist/src/app/auth/types.js +24 -0
  41. package/dist/src/app/auth/types.js.map +1 -0
  42. package/dist/src/app/index.d.ts +2 -3
  43. package/dist/src/app/index.js +2 -3
  44. package/dist/src/app/index.js.map +1 -1
  45. package/dist/src/app/inputFileDownloader.d.ts +10 -3
  46. package/dist/src/app/routeList.d.ts +1 -1
  47. package/dist/src/app/routeList.js +22 -5
  48. package/dist/src/app/routeList.js.map +1 -1
  49. package/dist/src/app/streaming/streamingResponse.d.ts +11 -1
  50. package/dist/src/app/streaming/streamingResponse.js +17 -2
  51. package/dist/src/app/streaming/streamingResponse.js.map +1 -1
  52. package/dist/src/app/turnState.d.ts +2 -38
  53. package/dist/src/app/turnState.js +1 -46
  54. package/dist/src/app/turnState.js.map +1 -1
  55. package/dist/src/auth/MemoryCache.d.ts +16 -0
  56. package/dist/src/auth/MemoryCache.js +58 -0
  57. package/dist/src/auth/MemoryCache.js.map +1 -0
  58. package/dist/src/auth/authConfiguration.d.ts +44 -2
  59. package/dist/src/auth/authConfiguration.js +218 -53
  60. package/dist/src/auth/authConfiguration.js.map +1 -1
  61. package/dist/src/auth/authConstants.d.ts +11 -0
  62. package/dist/src/auth/authConstants.js +15 -0
  63. package/dist/src/auth/authConstants.js.map +1 -0
  64. package/dist/src/auth/authProvider.d.ts +23 -0
  65. package/dist/src/auth/connections.d.ts +40 -0
  66. package/dist/src/auth/connections.js +7 -0
  67. package/dist/src/auth/connections.js.map +1 -0
  68. package/dist/src/auth/index.d.ts +2 -0
  69. package/dist/src/auth/index.js +2 -0
  70. package/dist/src/auth/index.js.map +1 -1
  71. package/dist/src/auth/jwt-middleware.js +31 -18
  72. package/dist/src/auth/jwt-middleware.js.map +1 -1
  73. package/dist/src/auth/msalConnectionManager.d.ts +63 -0
  74. package/dist/src/auth/msalConnectionManager.js +124 -0
  75. package/dist/src/auth/msalConnectionManager.js.map +1 -0
  76. package/dist/src/auth/msalTokenProvider.d.ts +31 -0
  77. package/dist/src/auth/msalTokenProvider.js +167 -16
  78. package/dist/src/auth/msalTokenProvider.js.map +1 -1
  79. package/dist/src/baseAdapter.d.ts +10 -25
  80. package/dist/src/baseAdapter.js +2 -15
  81. package/dist/src/baseAdapter.js.map +1 -1
  82. package/dist/src/cloudAdapter.d.ts +40 -23
  83. package/dist/src/cloudAdapter.js +132 -56
  84. package/dist/src/cloudAdapter.js.map +1 -1
  85. package/dist/src/connector-client/connectorClient.d.ts +9 -0
  86. package/dist/src/connector-client/connectorClient.js +39 -9
  87. package/dist/src/connector-client/connectorClient.js.map +1 -1
  88. package/dist/src/index.d.ts +0 -1
  89. package/dist/src/index.js +0 -1
  90. package/dist/src/index.js.map +1 -1
  91. package/dist/src/oauth/index.d.ts +0 -1
  92. package/dist/src/oauth/index.js +0 -1
  93. package/dist/src/oauth/index.js.map +1 -1
  94. package/dist/src/oauth/userTokenClient.d.ts +30 -13
  95. package/dist/src/oauth/userTokenClient.js +64 -26
  96. package/dist/src/oauth/userTokenClient.js.map +1 -1
  97. package/dist/src/oauth/userTokenClient.types.d.ts +19 -6
  98. package/dist/src/turnContext.d.ts +7 -1
  99. package/dist/src/turnContext.js +11 -4
  100. package/dist/src/turnContext.js.map +1 -1
  101. package/package.json +10 -6
  102. package/src/activityWireCompat.ts +8 -3
  103. package/src/agent-client/agentClient.ts +9 -3
  104. package/src/agent-client/agentResponseHandler.ts +5 -2
  105. package/src/app/agentApplication.ts +98 -79
  106. package/src/app/agentApplicationBuilder.ts +2 -2
  107. package/src/app/agentApplicationOptions.ts +10 -2
  108. package/src/app/appRoute.ts +8 -0
  109. package/src/app/attachmentDownloader.ts +18 -3
  110. package/src/app/auth/authorization.ts +252 -0
  111. package/src/app/auth/authorizationManager.ts +213 -0
  112. package/src/app/auth/handlerStorage.ts +61 -0
  113. package/src/app/auth/handlers/agenticAuthorization.ts +194 -0
  114. package/src/app/auth/handlers/azureBotAuthorization.ts +599 -0
  115. package/src/app/auth/handlers/index.ts +2 -0
  116. package/src/app/auth/index.ts +2 -0
  117. package/src/app/auth/types.ts +111 -0
  118. package/src/app/index.ts +2 -3
  119. package/src/app/inputFileDownloader.ts +11 -3
  120. package/src/app/routeList.ts +24 -5
  121. package/src/app/streaming/streamingResponse.ts +20 -3
  122. package/src/app/turnState.ts +2 -61
  123. package/src/auth/MemoryCache.ts +59 -0
  124. package/src/auth/authConfiguration.ts +258 -52
  125. package/src/auth/authConstants.ts +11 -0
  126. package/src/auth/authProvider.ts +31 -0
  127. package/src/auth/connections.ts +46 -0
  128. package/src/auth/index.ts +2 -0
  129. package/src/auth/jwt-middleware.ts +38 -21
  130. package/src/auth/msalConnectionManager.ts +150 -0
  131. package/src/auth/msalTokenProvider.ts +209 -9
  132. package/src/baseAdapter.ts +10 -29
  133. package/src/cloudAdapter.ts +192 -67
  134. package/src/connector-client/connectorClient.ts +49 -10
  135. package/src/index.ts +0 -1
  136. package/src/oauth/index.ts +0 -1
  137. package/src/oauth/userTokenClient.ts +79 -23
  138. package/src/oauth/userTokenClient.types.ts +20 -8
  139. package/src/turnContext.ts +16 -5
  140. package/dist/src/app/authorization.js +0 -387
  141. package/dist/src/app/authorization.js.map +0 -1
  142. package/dist/src/claimsIdentity.d.ts +0 -35
  143. package/dist/src/claimsIdentity.js +0 -43
  144. package/dist/src/claimsIdentity.js.map +0 -1
  145. package/dist/src/oauth/oAuthFlow.d.ts +0 -119
  146. package/dist/src/oauth/oAuthFlow.js +0 -316
  147. package/dist/src/oauth/oAuthFlow.js.map +0 -1
  148. package/src/app/authorization.ts +0 -432
  149. package/src/claimsIdentity.ts +0 -47
  150. package/src/oauth/oAuthFlow.ts +0 -378
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { Activity } from '@microsoft/agents-activity'
7
+ import { Storage, StoreItem } from '../../storage'
8
+ import { TurnContext } from '../../turnContext'
9
+ import { AgenticAuthorizationOptions, AzureBotAuthorizationOptions } from './handlers'
10
+ import { TokenResponse } from '../../oauth'
11
+ import { Connections } from '../../auth/connections'
12
+
13
+ /**
14
+ * Authorization configuration options.
15
+ */
16
+ export type AuthorizationOptions = Record<string, AzureBotAuthorizationOptions | AgenticAuthorizationOptions>
17
+
18
+ /**
19
+ * Represents the status of a handler registration attempt.
20
+ */
21
+ export enum AuthorizationHandlerStatus {
22
+ /** The handler has approved the request - validation passed */
23
+ APPROVED = 'approved',
24
+ /** The handler registration is pending further action */
25
+ PENDING = 'pending',
26
+ /** The handler has rejected the request - validation failed */
27
+ REJECTED = 'rejected',
28
+ /** The handler has ignored the request - no action taken */
29
+ IGNORED = 'ignored',
30
+ /** The handler requires revalidation */
31
+ REVALIDATE = 'revalidate'
32
+ }
33
+
34
+ /**
35
+ * Active handler manager information.
36
+ */
37
+ export interface ActiveAuthorizationHandler extends StoreItem {
38
+ /**
39
+ * Unique identifier for the handler.
40
+ */
41
+ readonly id: string
42
+ /**
43
+ * The current activity associated with the handler.
44
+ */
45
+ activity: Activity
46
+ }
47
+
48
+ export interface AuthorizationHandler {
49
+ /**
50
+ * Unique identifier for the handler.
51
+ */
52
+ readonly id: string
53
+ /**
54
+ * Initiates the sign-in process for the handler.
55
+ * @param context The turn context.
56
+ * @param active Optional active handler data.
57
+ * @returns The status of the sign-in attempt.
58
+ */
59
+ signin(context: TurnContext, active?: ActiveAuthorizationHandler): Promise<AuthorizationHandlerStatus>
60
+ /**
61
+ * Initiates the sign-out process for the handler.
62
+ * @param context The turn context.
63
+ * @returns A promise that resolves to a boolean indicating the success of the sign-out attempt.
64
+ */
65
+ signout(context: TurnContext): Promise<boolean>;
66
+ /**
67
+ * Retrieves an access token for the specified scopes.
68
+ * @param context The turn context.
69
+ * @param options Optional token request options.
70
+ * @returns The access token response.
71
+ */
72
+ token(context: TurnContext, options?: AuthorizationHandlerTokenOptions): Promise<TokenResponse>;
73
+ /**
74
+ * Registers a callback to be invoked when the sign-in process is successful.
75
+ * @param callback The callback to invoke on success.
76
+ */
77
+ onSuccess(callback: (context: TurnContext) => Promise<void> | void): void;
78
+ /**
79
+ * Registers a callback to be invoked when the sign-in process fails.
80
+ * @param callback The callback to invoke on failure.
81
+ */
82
+ onFailure(callback: (context: TurnContext, reason?: string) => Promise<void> | void): void;
83
+ }
84
+
85
+ /**
86
+ * Common settings required by authorization handlers.
87
+ */
88
+ export interface AuthorizationHandlerSettings {
89
+ /**
90
+ * Storage instance for persisting handler state.
91
+ */
92
+ storage: Storage
93
+ /**
94
+ * Connections instance for managing authentication connections.
95
+ */
96
+ connections: Connections
97
+ }
98
+
99
+ /**
100
+ * Options for token requests in authorization handlers.
101
+ */
102
+ export interface AuthorizationHandlerTokenOptions {
103
+ /**
104
+ * Optional name of the connection to use for the token request. Usually used for OBO flows.
105
+ */
106
+ connection?: string
107
+ /**
108
+ * Optional scopes to request in the token. Usually used for OBO flows.
109
+ */
110
+ scopes?: string[]
111
+ }
package/src/app/index.ts CHANGED
@@ -2,13 +2,12 @@ export * from './agentApplication'
2
2
  export * from './agentApplicationBuilder'
3
3
  export * from './agentApplicationOptions'
4
4
  export * from './appRoute'
5
- export * from './attachmentDownloader'
6
- export * from './authorization'
7
- export * from './conversationUpdateEvents'
8
5
  export * from './routeHandler'
9
6
  export * from './routeList'
10
7
  export * from './routeRank'
11
8
  export * from './routeSelector'
9
+ export * from './attachmentDownloader'
10
+ export * from './conversationUpdateEvents'
12
11
  export * from './turnState'
13
12
  export * from './turnEvents'
14
13
  export * from './turnStateEntry'
@@ -31,11 +31,19 @@ export interface InputFile {
31
31
  */
32
32
  export interface InputFileDownloader<TState extends TurnState = TurnState> {
33
33
  /**
34
- * Downloads files based on the provided turn context and state.
34
+ * Downloads files based on the provided turn context.
35
35
  *
36
36
  * @param context - The turn context for the current operation.
37
- * @param state - The state associated with the current turn.
38
37
  * @returns A promise that resolves to an array of input files.
39
38
  */
40
- downloadFiles(context: TurnContext, state: TState): Promise<InputFile[]>;
39
+ downloadFiles(context: TurnContext): Promise<InputFile[]>;
40
+
41
+ /**
42
+ * Downloads files based on the provided turn context and store them in the provided state.
43
+ *
44
+ * @param context - The turn context for the current operation.
45
+ * @param state - The state associated with the current turn.
46
+ * @returns A promise that resolves once the files have been saved into state (void).
47
+ */
48
+ downloadAndStoreFiles(context: TurnContext, state: TState): Promise<void>;
41
49
  }
@@ -17,17 +17,36 @@ export class RouteList<TState extends TurnState> {
17
17
  handler: RouteHandler<TState>,
18
18
  isInvokeRoute: boolean = false,
19
19
  rank: number = RouteRank.Unspecified,
20
- authHandlers: string[] = []
20
+ authHandlers: string[] = [],
21
+ isAgenticRoute: boolean = false
21
22
  ): this {
22
- this._routes.push({ selector, handler, isInvokeRoute, rank, authHandlers })
23
+ this._routes.push({ selector, handler, isInvokeRoute, rank, authHandlers, isAgenticRoute })
23
24
 
24
- // Invoke selectors are first, then order by rank ascending
25
+ // Ordered by:
26
+ // Agentic + Invoke
27
+ // Invoke
28
+ // Agentic
29
+ // Other
30
+ // Then by Rank
25
31
  this._routes.sort((a, b) => {
26
- if (a.isInvokeRoute !== b.isInvokeRoute) {
27
- return a.isInvokeRoute ? -1 : 1
32
+ const getPriority = (route: AppRoute<TState>) => {
33
+ if (route.isAgenticRoute && route.isInvokeRoute) return 0
34
+ if (route.isInvokeRoute) return 1
35
+ if (route.isAgenticRoute) return 2
36
+ return 3
28
37
  }
38
+
39
+ const priorityA = getPriority(a)
40
+ const priorityB = getPriority(b)
41
+
42
+ if (priorityA !== priorityB) {
43
+ return priorityA - priorityB
44
+ }
45
+
46
+ // If priorities are equal, sort by rank
29
47
  return (a.rank ?? 0) - (b.rank ?? 0)
30
48
  })
49
+
31
50
  return this
32
51
  }
33
52
 
@@ -29,6 +29,7 @@ export class StreamingResponse {
29
29
  private _message: string = ''
30
30
  private _attachments?: Attachment[]
31
31
  private _ended = false
32
+ private _delayInMs = 1500
32
33
 
33
34
  // Queue for outgoing activities
34
35
  private _queue: Array<() => Activity> = []
@@ -80,6 +81,13 @@ export class StreamingResponse {
80
81
  return this._nextSequence - 1
81
82
  }
82
83
 
84
+ /**
85
+ * Gets the delay in milliseconds between chunks.
86
+ */
87
+ public get delayInMs (): number {
88
+ return this._delayInMs
89
+ }
90
+
83
91
  /**
84
92
  * Queues an informative update to be sent to the client.
85
93
  *
@@ -183,7 +191,8 @@ export class StreamingResponse {
183
191
  appearance: {
184
192
  '@type': 'DigitalDocument',
185
193
  name: citation.title || `Document #${currPos + 1}`,
186
- abstract: CitationUtil.snippet(citation.content, 477)
194
+ abstract: CitationUtil.snippet(citation.content, 477),
195
+ url: citation.url!
187
196
  }
188
197
  }
189
198
  currPos++
@@ -222,6 +231,14 @@ export class StreamingResponse {
222
231
  this._enableGeneratedByAILabel = enableGeneratedByAILabel
223
232
  }
224
233
 
234
+ /**
235
+ * Sets the delay in milliseconds between chunks.
236
+ * @param delayInMs The delay in milliseconds.
237
+ */
238
+ public setDelayInMs (delayInMs: number): void {
239
+ this._delayInMs = delayInMs
240
+ }
241
+
225
242
  /**
226
243
  * Returns the most recently streamed message.
227
244
  *
@@ -236,7 +253,7 @@ export class StreamingResponse {
236
253
  *
237
254
  * @returns {Promise<void>} - A promise representing the async operation.
238
255
  */
239
- public waitForQueue (): Promise<void> {
256
+ private waitForQueue (): Promise<void> {
240
257
  return this._queueSync || Promise.resolve()
241
258
  }
242
259
 
@@ -370,7 +387,7 @@ export class StreamingResponse {
370
387
 
371
388
  // Send activity
372
389
  const response = await this._context.sendActivity(activity)
373
- await new Promise((resolve) => setTimeout(resolve, 1500))
390
+ await new Promise((resolve) => setTimeout(resolve, this.delayInMs))
374
391
 
375
392
  // Save assigned stream ID
376
393
  if (!this._streamId) {
@@ -5,7 +5,6 @@
5
5
 
6
6
  import { Storage, StoreItems } from '../storage'
7
7
  import { AppMemory } from './appMemory'
8
- import { InputFile } from './inputFileDownloader'
9
8
  import { TurnStateEntry } from './turnStateEntry'
10
9
  import { TurnContext } from '../turnContext'
11
10
  import { debug } from '@microsoft/agents-activity/logger'
@@ -30,22 +29,11 @@ export interface DefaultConversationState {}
30
29
  */
31
30
  export interface DefaultUserState {}
32
31
 
33
- /**
34
- * Default interface for temporary state that persists only during the current turn.
35
- * Contains properties used for handling user input, file attachments, and OAuth flows.
36
- */
37
- export interface DefaultTempState {
38
- /** Collection of files attached to the current message */
39
- inputFiles: InputFile[];
40
- }
41
-
42
32
  /**
43
33
  * Base class defining a collection of turn state scopes.
44
34
  *
45
35
  * @typeParam TConversationState - Type for conversation-scoped state
46
36
  * @typeParam TUserState - Type for user-scoped state
47
- * @typeParam TTempState - Type for temporary state that exists only for the current turn
48
- * @typeParam TSSOState - Type for Single Sign-On (SSO) state
49
37
  *
50
38
  * @remarks
51
39
  * Developers can create a derived class that extends `TurnState` to add additional state scopes.
@@ -80,8 +68,7 @@ export interface DefaultTempState {
80
68
  */
81
69
  export class TurnState<
82
70
  TConversationState = DefaultConversationState,
83
- TUserState = DefaultUserState,
84
- TTempState = DefaultTempState
71
+ TUserState = DefaultUserState
85
72
  > implements AppMemory {
86
73
  private _scopes: Record<string, TurnStateEntry> = {}
87
74
  private _isLoaded = false
@@ -128,37 +115,6 @@ export class TurnState<
128
115
  return this._isLoaded
129
116
  }
130
117
 
131
- /**
132
- * Gets the temporary state for the current turn.
133
- *
134
- * @returns The temporary state object
135
- * @throws Error if state hasn't been loaded
136
- *
137
- * @remarks
138
- * This state is not persisted between turns.
139
- */
140
- public get temp (): TTempState {
141
- const scope = this.getScope(TEMP_SCOPE)
142
- if (!scope) {
143
- throw new Error(this._stateNotLoadedString)
144
- }
145
- return scope.value as TTempState
146
- }
147
-
148
- /**
149
- * Sets the temporary state for the current turn.
150
- *
151
- * @param value - The new temporary state object
152
- * @throws Error if state hasn't been loaded
153
- */
154
- public set temp (value: TTempState) {
155
- const scope = this.getScope(TEMP_SCOPE)
156
- if (!scope) {
157
- throw new Error(this._stateNotLoadedString)
158
- }
159
- scope.replace(value as Record<string, unknown>)
160
- }
161
-
162
118
  /**
163
119
  * Gets the user-scoped state.
164
120
  *
@@ -206,22 +162,6 @@ export class TurnState<
206
162
  scope.delete()
207
163
  }
208
164
 
209
- /**
210
- * Marks the temporary state for deletion.
211
- *
212
- * @throws Error if state hasn't been loaded
213
- *
214
- * @remarks
215
- * Since temporary state is not persisted, this just clears the in-memory object.
216
- */
217
- public deleteTempState (): void {
218
- const scope = this.getScope(TEMP_SCOPE)
219
- if (!scope) {
220
- throw new Error(this._stateNotLoadedString)
221
- }
222
- scope.delete()
223
- }
224
-
225
165
  /**
226
166
  * Marks the user state for deletion.
227
167
  *
@@ -255,6 +195,7 @@ export class TurnState<
255
195
  *
256
196
  * @remarks
257
197
  * Format: "scope.property" or just "property" (defaults to temp scope)
198
+ * The temp scope is internal-only, not persisted to storage, and exists only for the current turn.
258
199
  */
259
200
  public deleteValue (path: string): void {
260
201
  const { scope, name } = this.getScopeAndName(path)
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ const CACHE_PURGE_INTERVAL = 60000 // 60 seconds
6
+
7
+ /**
8
+ * Simple in-memory cache with TTL support.
9
+ * This is used to store authentication tokens for Agentic Identity scenarios only!
10
+ */
11
+ export class MemoryCache<T> {
12
+ private cache = new Map<string, { value: T; validUntil: number }>()
13
+ private purgeInterval?: NodeJS.Timeout
14
+
15
+ /**
16
+ * Clears the purge interval to allow the process to exit cleanly
17
+ */
18
+ destroy (): void {
19
+ if (this.purgeInterval) {
20
+ clearInterval(this.purgeInterval)
21
+ this.purgeInterval = undefined
22
+ }
23
+ }
24
+
25
+ set (key: string, value: T, ttlSeconds: number): void {
26
+ const validUntil = Date.now() + (ttlSeconds * 1000)
27
+ this.cache.set(key, { value, validUntil })
28
+ if (!this.purgeInterval) {
29
+ this.purgeInterval = setInterval(() => this.purge(), CACHE_PURGE_INTERVAL)
30
+ }
31
+ }
32
+
33
+ get (key: string): T | undefined {
34
+ const item = this.cache.get(key)
35
+ if (!item) {
36
+ return undefined
37
+ }
38
+
39
+ // Check if item has expired
40
+ if (Date.now() > item.validUntil) {
41
+ this.cache.delete(key)
42
+ return undefined
43
+ }
44
+ return item.value
45
+ }
46
+
47
+ delete (key: string): boolean {
48
+ return this.cache.delete(key)
49
+ }
50
+
51
+ purge (): void {
52
+ const now = Date.now()
53
+ for (const [key, { validUntil }] of this.cache.entries()) {
54
+ if (now > validUntil) {
55
+ this.cache.delete(key)
56
+ }
57
+ }
58
+ }
59
+ }