@iworkforces/tracelattice 2.0.5 → 2.0.7
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 +27 -0
- package/dist/cli.js +108 -32
- package/dist/config/ConfigLoader.d.ts +3 -0
- package/dist/config/ConfigLoader.d.ts.map +1 -1
- package/dist/config/ConfigLoader.js +6 -0
- package/dist/config/ConfigLoader.js.map +1 -1
- package/dist/core/compression/CompressionService.d.ts +4 -3
- package/dist/core/compression/CompressionService.d.ts.map +1 -1
- package/dist/core/compression/CompressionService.js +19 -10
- package/dist/core/compression/CompressionService.js.map +1 -1
- package/dist/core/compression/DehydrationPolicy.d.ts +4 -4
- package/dist/core/compression/DehydrationPolicy.d.ts.map +1 -1
- package/dist/core/compression/DehydrationPolicy.js +3 -6
- package/dist/core/compression/DehydrationPolicy.js.map +1 -1
- package/dist/lib.d.ts +6 -6
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +1 -0
- package/dist/lib.js.map +1 -1
- package/dist/metrics/metrics.impl.d.ts +2 -7
- package/dist/metrics/metrics.impl.d.ts.map +1 -1
- package/dist/metrics/metrics.impl.js +32 -34
- package/dist/metrics/metrics.impl.js.map +1 -1
- package/dist/pool/ConnectionPool.d.ts +7 -3
- package/dist/pool/ConnectionPool.d.ts.map +1 -1
- package/dist/pool/ConnectionPool.js +65 -40
- package/dist/pool/ConnectionPool.js.map +1 -1
- package/dist/transport/StreamableHttpTransport.d.ts +12 -0
- package/dist/transport/StreamableHttpTransport.d.ts.map +1 -1
- package/dist/transport/StreamableHttpTransport.js +95 -24
- package/dist/transport/StreamableHttpTransport.js.map +1 -1
- package/package.json +5 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConnectionPool.d.ts","sourceRoot":"","sources":["../../src/pool/ConnectionPool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;
|
|
1
|
+
{"version":3,"file":"ConnectionPool.d.ts","sourceRoot":"","sources":["../../src/pool/ConnectionPool.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,7 +15,8 @@ class Session {
|
|
|
15
15
|
_closePromise = null;
|
|
16
16
|
_activeOperationCount = 0;
|
|
17
17
|
_operationDrain = null;
|
|
18
|
-
|
|
18
|
+
_onTimeout;
|
|
19
|
+
constructor(id, server, timeout, logger, onTimeout = null){
|
|
19
20
|
this._server = server;
|
|
20
21
|
this._id = id;
|
|
21
22
|
this._createdAt = Date.now();
|
|
@@ -23,6 +24,7 @@ class Session {
|
|
|
23
24
|
this._isActiveValue = true;
|
|
24
25
|
this._timeout = timeout;
|
|
25
26
|
this._logger = logger;
|
|
27
|
+
this._onTimeout = onTimeout;
|
|
26
28
|
this._startTimeout();
|
|
27
29
|
}
|
|
28
30
|
get isActive() {
|
|
@@ -72,6 +74,9 @@ class Session {
|
|
|
72
74
|
if (this._closePromise) return this._closePromise;
|
|
73
75
|
const completion = Promise.withResolvers();
|
|
74
76
|
this._closePromise = completion.promise;
|
|
77
|
+
completion.promise.then(void 0, ()=>{
|
|
78
|
+
if (this._closePromise === completion.promise) this._closePromise = null;
|
|
79
|
+
});
|
|
75
80
|
this._isActiveValue = false;
|
|
76
81
|
if (this._cleanupTimer) {
|
|
77
82
|
clearTimeout(this._cleanupTimer);
|
|
@@ -98,7 +103,8 @@ class Session {
|
|
|
98
103
|
this._cleanupTimer = setTimeout(()=>{
|
|
99
104
|
if (this.isTimedOut()) {
|
|
100
105
|
this._logger.warn(`Session ${this._id} timed out, closing`);
|
|
101
|
-
this.
|
|
106
|
+
if (this._onTimeout) this._onTimeout(this._id);
|
|
107
|
+
else this.close().catch((err)=>{
|
|
102
108
|
this._logger.error(`Error closing timed out session ${this._id}:`, err);
|
|
103
109
|
});
|
|
104
110
|
}
|
|
@@ -110,6 +116,7 @@ class Session {
|
|
|
110
116
|
}
|
|
111
117
|
class ConnectionPool {
|
|
112
118
|
_sessions = new Map();
|
|
119
|
+
_ownedSessions = new Map();
|
|
113
120
|
_closingSessions = new Map();
|
|
114
121
|
_createSessionLock = null;
|
|
115
122
|
_maxSessions;
|
|
@@ -120,9 +127,8 @@ class ConnectionPool {
|
|
|
120
127
|
_terminated = false;
|
|
121
128
|
_logger;
|
|
122
129
|
_serverFactory;
|
|
123
|
-
|
|
130
|
+
_terminationGeneration = null;
|
|
124
131
|
_admissionContext = new AsyncLocalStorage();
|
|
125
|
-
_pendingCreateStopFailures = new Set();
|
|
126
132
|
constructor(options = {}){
|
|
127
133
|
this._maxSessions = options.maxSessions ?? 100;
|
|
128
134
|
this._sessionTimeout = options.sessionTimeout ?? 300000;
|
|
@@ -145,7 +151,7 @@ class ConnectionPool {
|
|
|
145
151
|
async createSession() {
|
|
146
152
|
while(this._createSessionLock)await this._createSessionLock;
|
|
147
153
|
if (this._terminated) throw new PoolTerminatedError();
|
|
148
|
-
if (this.
|
|
154
|
+
if (this._ownedSessions.size >= this._maxSessions) throw new MaxSessionsReachedError(this._maxSessions);
|
|
149
155
|
if (!this._serverFactory) throw new Error('ConnectionPool requires a serverFactory option to create sessions');
|
|
150
156
|
let resolveLock;
|
|
151
157
|
this._createSessionLock = new Promise((resolve)=>{
|
|
@@ -153,17 +159,16 @@ class ConnectionPool {
|
|
|
153
159
|
});
|
|
154
160
|
try {
|
|
155
161
|
const sessionId = asSessionId(`session_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`);
|
|
162
|
+
if (this._ownedSessions.has(sessionId)) throw new Error(`Session ID collision: ${sessionId}`);
|
|
156
163
|
const server = await this._serverFactory();
|
|
164
|
+
const session = new Session(sessionId, server, this._sessionTimeout, this._logger, (timedOutSessionId)=>this._handleSessionTimeout(timedOutSessionId));
|
|
165
|
+
this._ownedSessions.set(sessionId, session);
|
|
157
166
|
if (this._terminated) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
this._pendingCreateStopFailures.add(error);
|
|
162
|
-
throw error;
|
|
163
|
-
}
|
|
167
|
+
const generation = this._terminationGeneration;
|
|
168
|
+
if (generation) await this._coordinateCleanup(sessionId, generation);
|
|
169
|
+
else await this._coordinateCleanup(sessionId);
|
|
164
170
|
throw new PoolTerminatedError();
|
|
165
171
|
}
|
|
166
|
-
const session = new Session(sessionId, server, this._sessionTimeout, this._logger);
|
|
167
172
|
this._sessions.set(sessionId, session);
|
|
168
173
|
this._logger.info(`Created session ${sessionId} (${this._sessions.size}/${this._maxSessions} active sessions)`);
|
|
169
174
|
return sessionId;
|
|
@@ -195,7 +200,7 @@ class ConnectionPool {
|
|
|
195
200
|
value: await operation(activeFrame.session)
|
|
196
201
|
}))();
|
|
197
202
|
const session = this._sessions.get(sessionId);
|
|
198
|
-
if (!session) return Promise.resolve(this.
|
|
203
|
+
if (!session) return Promise.resolve(this._ownedSessions.has(sessionId) ? {
|
|
199
204
|
status: 'inactive'
|
|
200
205
|
} : {
|
|
201
206
|
status: 'missing'
|
|
@@ -216,20 +221,35 @@ class ConnectionPool {
|
|
|
216
221
|
});
|
|
217
222
|
}
|
|
218
223
|
closeSession(sessionId) {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
224
|
+
if (!this._ownedSessions.has(sessionId)) return Promise.reject(new SessionNotFoundError(sessionId));
|
|
225
|
+
const generation = this._terminationGeneration;
|
|
226
|
+
return generation ? this._coordinateCleanup(sessionId, generation) : this._coordinateCleanup(sessionId);
|
|
227
|
+
}
|
|
228
|
+
_coordinateCleanup(sessionId, generation) {
|
|
229
|
+
const generationAttempt = generation?.attempts.get(sessionId);
|
|
230
|
+
if (generationAttempt) return generationAttempt;
|
|
231
|
+
const session = this._ownedSessions.get(sessionId);
|
|
222
232
|
if (!session) return Promise.reject(new SessionNotFoundError(sessionId));
|
|
223
233
|
this._sessions.delete(sessionId);
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
234
|
+
const currentAttempt = this._closingSessions.get(sessionId);
|
|
235
|
+
const attempt = currentAttempt ?? session.close();
|
|
236
|
+
if (!currentAttempt) this._closingSessions.set(sessionId, attempt);
|
|
237
|
+
generation?.attempts.set(sessionId, attempt);
|
|
238
|
+
if (!currentAttempt) attempt.then(()=>{
|
|
239
|
+
if (this._closingSessions.get(sessionId) === attempt) this._closingSessions.delete(sessionId);
|
|
240
|
+
if (this._ownedSessions.get(sessionId) === session) this._ownedSessions.delete(sessionId);
|
|
228
241
|
this._logger.info(`Closed session ${sessionId} (${this._sessions.size}/${this._maxSessions} active sessions)`);
|
|
229
242
|
}, ()=>{
|
|
230
|
-
this._closingSessions.delete(sessionId);
|
|
243
|
+
if (this._closingSessions.get(sessionId) === attempt) this._closingSessions.delete(sessionId);
|
|
244
|
+
});
|
|
245
|
+
return attempt;
|
|
246
|
+
}
|
|
247
|
+
_handleSessionTimeout(sessionId) {
|
|
248
|
+
const generation = this._terminationGeneration;
|
|
249
|
+
const attempt = generation ? this._coordinateCleanup(sessionId, generation) : this._coordinateCleanup(sessionId);
|
|
250
|
+
attempt.catch((err)=>{
|
|
251
|
+
this._logger.error(`Error closing timed out session ${sessionId}:`, err);
|
|
231
252
|
});
|
|
232
|
-
return closePromise;
|
|
233
253
|
}
|
|
234
254
|
getSessionInfo(sessionId) {
|
|
235
255
|
return this._sessions.get(sessionId)?.getInfo();
|
|
@@ -255,8 +275,8 @@ class ConnectionPool {
|
|
|
255
275
|
}
|
|
256
276
|
_cleanupTimedOutSessions() {
|
|
257
277
|
let cleaned = 0;
|
|
258
|
-
for (const [sessionId, session] of this.
|
|
259
|
-
this.
|
|
278
|
+
for (const [sessionId, session] of this._ownedSessions.entries())if (session.isTimedOut()) {
|
|
279
|
+
this._coordinateCleanup(sessionId).catch((err)=>{
|
|
260
280
|
this._logger.error(`Error closing timed out session ${sessionId}:`, err);
|
|
261
281
|
});
|
|
262
282
|
cleaned++;
|
|
@@ -264,30 +284,35 @@ class ConnectionPool {
|
|
|
264
284
|
if (cleaned > 0) this._logger.info(`Cleaned ${cleaned} timed-out sessions (${this._sessions.size}/${this._maxSessions} active sessions)`);
|
|
265
285
|
}
|
|
266
286
|
terminate() {
|
|
267
|
-
if (this.
|
|
287
|
+
if (this._terminationGeneration) return this._terminationGeneration.completion.promise;
|
|
268
288
|
const completion = Promise.withResolvers();
|
|
269
|
-
|
|
289
|
+
const generation = {
|
|
290
|
+
attempts: new Map(),
|
|
291
|
+
completion
|
|
292
|
+
};
|
|
293
|
+
this._terminationGeneration = generation;
|
|
270
294
|
this._terminated = true;
|
|
271
295
|
if (null !== this._cleanupTimerId) {
|
|
272
296
|
clearInterval(this._cleanupTimerId);
|
|
273
297
|
this._cleanupTimerId = null;
|
|
274
298
|
}
|
|
275
|
-
const
|
|
276
|
-
this.
|
|
277
|
-
|
|
278
|
-
if (this._createSessionLock) closePromises.add(this._createSessionLock);
|
|
279
|
-
for (const session of sessions)closePromises.add(session.close());
|
|
280
|
-
Promise.allSettled(closePromises).then((outcomes)=>{
|
|
281
|
-
const failures = [
|
|
282
|
-
...this._pendingCreateStopFailures,
|
|
283
|
-
...outcomes.filter((outcome)=>'rejected' === outcome.status).map((outcome)=>outcome.reason)
|
|
284
|
-
];
|
|
285
|
-
if (failures.length > 0) return void completion.reject(new AggregateError(failures, 'ConnectionPool termination failed'));
|
|
286
|
-
this._logger.info('ConnectionPool terminated');
|
|
287
|
-
completion.resolve();
|
|
288
|
-
});
|
|
299
|
+
const pendingCreate = this._createSessionLock;
|
|
300
|
+
for (const sessionId of this._ownedSessions.keys())this._coordinateCleanup(sessionId, generation);
|
|
301
|
+
this._completeTerminationGeneration(generation, pendingCreate);
|
|
289
302
|
return completion.promise;
|
|
290
303
|
}
|
|
304
|
+
async _completeTerminationGeneration(generation, pendingCreate) {
|
|
305
|
+
if (pendingCreate) await pendingCreate;
|
|
306
|
+
const outcomes = await Promise.allSettled(generation.attempts.values());
|
|
307
|
+
const failures = outcomes.filter((outcome)=>'rejected' === outcome.status).map((outcome)=>outcome.reason);
|
|
308
|
+
if (failures.length > 0) {
|
|
309
|
+
if (this._terminationGeneration === generation) this._terminationGeneration = null;
|
|
310
|
+
generation.completion.reject(new AggregateError(failures, 'ConnectionPool termination failed'));
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
this._logger.info('ConnectionPool terminated');
|
|
314
|
+
generation.completion.resolve();
|
|
315
|
+
}
|
|
291
316
|
dispose() {
|
|
292
317
|
return this.terminate();
|
|
293
318
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pool/ConnectionPool.js","sources":["../../src/pool/ConnectionPool.ts"],"sourcesContent":["/**\n * Connection Pool for managing concurrent user sessions.\n *\n * This module provides session management for multi-user scenarios,\n * allowing multiple concurrent clients to each have isolated state.\n *\n * @example\n * ```typescript\n * const pool = new ConnectionPool({\n * maxSessions: 100,\n * sessionTimeout: 300000 // 5 minutes\n * });\n *\n * const sessionId = await pool.createSession();\n * await pool.process(sessionId, thought);\n * await pool.closeSession(sessionId);\n * ```\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport {\n\tMaxSessionsReachedError,\n\tPoolTerminatedError,\n} from '../errors.js';\nimport type { Logger } from '../logger/StructuredLogger.js';\nimport { asSessionId, type SessionId } from '../contracts/ids.js';\nimport { assertNever } from '../utils.js';\nimport { SessionNotActiveError, SessionNotFoundError } from './PoolErrors.js';\nimport type {\n\tConnectionPoolStats,\n\tIConnectionPool,\n\tProcessResult,\n\tSessionInfo,\n\tSessionRunResult,\n\tSessionServer,\n\tSessionThoughtInput,\n} from './IConnectionPool.js';\n\nexport interface SessionOptions {\n\t/**\n\t * Maximum number of concurrent sessions\n\t * @default 100\n\t */\n\tmaxSessions?: number;\n\n\t/**\n\t * Logger instance\n\t */\n\tlogger?: Logger;\n\n\tserverFactory?: () => Promise<SessionServer>;\n\n\t/**\n\t * Session timeout in milliseconds\n\t * @default 300000 (5 minutes)\n\t */\n\tsessionTimeout?: number;\n\n\t/**\n\t * Whether to enable automatic session cleanup\n\t * @default true\n\t */\n\tautoCleanup?: boolean;\n\n\t/**\n\t * Cleanup interval in milliseconds\n\t * @default 60000 (1 minute)\n\t */\n\tcleanupInterval?: number;\n}\n\n/**\n * Represents a user session with its own server instance.\n */\nexport class Session {\n\tprivate _server: SessionServer;\n\tprivate _id: SessionId;\n\tprivate _createdAt: number;\n\tprivate _lastActivityAt: number;\n\tprivate _isActiveValue: boolean;\n\tprivate _timeout: number;\n\tprivate _cleanupTimer: NodeJS.Timeout | null = null;\n\tprivate _logger: Logger;\n\tprivate _closePromise: Promise<void> | null = null;\n\tprivate _activeOperationCount = 0;\n\tprivate _operationDrain: PromiseWithResolvers<void> | null = null;\n\n\tconstructor(id: SessionId, server: SessionServer, timeout: number, logger: Logger) {\n\t\tthis._server = server;\n\t\tthis._id = id;\n\t\tthis._createdAt = Date.now();\n\t\tthis._lastActivityAt = this._createdAt;\n\t\tthis._isActiveValue = true;\n\t\tthis._timeout = timeout;\n\t\tthis._logger = logger;\n\n\t\t// Start session timeout timer\n\t\tthis._startTimeout();\n\t}\n\n\t/**\n\t * Check if the session is active.\n\t */\n\tget isActive(): boolean {\n\t\treturn this._isActiveValue;\n\t}\n\n\t/**\n\t * Process a thought through this session's server instance.\n\t */\n\tasync process(input: SessionThoughtInput): Promise<ProcessResult> {\n\t\tif (!this.isActive) {\n\t\t\tthrow new SessionNotActiveError(this._id);\n\t\t}\n\n\t\t// Update last activity\n\t\tthis._lastActivityAt = Date.now();\n\n\t\t// Reset timeout timer\n\t\tthis._resetTimeout();\n\n\t\t// Process the thought\n\t\treturn this._server.processThought(input);\n\t}\n\n\tprocessAdmitted(input: SessionThoughtInput): Promise<ProcessResult> {\n\t\tthis._lastActivityAt = Date.now();\n\t\tif (this.isActive) this._resetTimeout();\n\t\treturn this._server.processThought(input);\n\t}\n\n\trunWhileActive<T>(operation: (session: Session) => Promise<T>): Promise<SessionRunResult<T>> {\n\t\tif (!this.isActive) return Promise.resolve({ status: 'inactive' });\n\t\tthis._activeOperationCount++;\n\t\treturn (async () => {\n\t\t\ttry {\n\t\t\t\treturn { status: 'completed', value: await operation(this) };\n\t\t\t} finally {\n\t\t\t\tthis._activeOperationCount--;\n\t\t\t\tif (this._activeOperationCount === 0) this._operationDrain?.resolve();\n\t\t\t}\n\t\t})();\n\t}\n\n\t/**\n\t * Get session information.\n\t */\n\tgetInfo(): SessionInfo {\n\t\treturn {\n\t\t\tid: this._id,\n\t\t\tserver: this._server,\n\t\t\tcreatedAt: this._createdAt,\n\t\t\tlastActivityAt: this._lastActivityAt,\n\t\t\tisActive: this.isActive,\n\t\t};\n\t}\n\n\t/**\n\t * Check if the session has timed out.\n\t */\n\tisTimedOut(): boolean {\n\t\treturn Date.now() - this._lastActivityAt > this._timeout;\n\t}\n\n\t/**\n\t * Close the session and stop the server.\n\t */\n\tclose(): Promise<void> {\n\t\tif (this._closePromise) {\n\t\t\treturn this._closePromise;\n\t\t}\n\n\t\tconst completion = Promise.withResolvers<void>();\n\t\tthis._closePromise = completion.promise;\n\t\tthis._isActiveValue = false;\n\n\t\t// Stop timeout timer\n\t\tif (this._cleanupTimer) {\n\t\t\tclearTimeout(this._cleanupTimer);\n\t\t\tthis._cleanupTimer = null;\n\t\t}\n\n\t\tif (this._activeOperationCount === 0) {\n\t\t\tthis._stopServer(completion);\n\t\t} else {\n\t\t\tthis._operationDrain = Promise.withResolvers<void>();\n\t\t\tvoid this._operationDrain.promise.then(() => this._stopServer(completion));\n\t\t}\n\n\t\treturn completion.promise;\n\t}\n\n\tprivate _stopServer(completion: PromiseWithResolvers<void>): void {\n\t\ttry {\n\t\t\tPromise.resolve(this._server.stop()).then(completion.resolve, completion.reject);\n\t\t} catch (error) {\n\t\t\tcompletion.reject(\n\t\t\t\terror instanceof Error ? error : new AggregateError([error], 'Session stop failed')\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Start the session timeout timer.\n\t */\n\tprivate _startTimeout(): void {\n\t\tif (this._cleanupTimer) {\n\t\t\tclearTimeout(this._cleanupTimer);\n\t\t}\n\n\t\tthis._cleanupTimer = setTimeout(() => {\n\t\t\tif (this.isTimedOut()) {\n\t\t\t\tthis._logger.warn(`Session ${this._id} timed out, closing`);\n\t\t\t\tthis.close().catch((err) => {\n\t\t\t\t\tthis._logger.error(`Error closing timed out session ${this._id}:`, err);\n\t\t\t\t});\n\t\t\t}\n\t\t}, this._timeout);\n\t}\n\n\t/**\n\t * Reset the timeout timer after activity.\n\t */\n\tprivate _resetTimeout(): void {\n\t\tthis._startTimeout();\n\t}\n}\n\n/**\n * ConnectionPool manages multiple concurrent user sessions.\n *\n * Each session has its own server instance with isolated state,\n * allowing multiple users to interact with the system simultaneously.\n */\nexport class ConnectionPool implements IConnectionPool {\n\tprivate _sessions: Map<SessionId, Session> = new Map();\n\tprivate readonly _closingSessions = new Map<SessionId, Promise<void>>();\n\tprivate _createSessionLock: Promise<void> | null = null;\n\tprivate _maxSessions: number;\n\tprivate _sessionTimeout: number;\n\tprivate _autoCleanup: boolean;\n\tprivate _cleanupInterval: number;\n\tprivate _cleanupTimerId: ReturnType<typeof setInterval> | null = null;\n\tprivate _terminated: boolean = false;\n\tprivate _logger: Logger;\n\tprivate _serverFactory: (() => Promise<SessionServer>) | null;\n\tprivate _terminatePromise: Promise<void> | null = null;\n\tprivate readonly _admissionContext = new AsyncLocalStorage<{\n\t\treadonly sessionId: SessionId;\n\t\treadonly session: Session;\n\t\topen: boolean;\n\t}>();\n\tprivate readonly _pendingCreateStopFailures = new Set<unknown>();\n\n\tconstructor(options: SessionOptions = {}) {\n\t\tthis._maxSessions = options.maxSessions ?? 100;\n\t\tthis._sessionTimeout = options.sessionTimeout ?? 300000; // 5 minutes\n\t\tthis._autoCleanup = options.autoCleanup ?? true;\n\t\tthis._cleanupInterval = options.cleanupInterval ?? 60000; // 1 minute\n\t\tthis._serverFactory = options.serverFactory ?? null;\n\t\tthis._logger = options.logger ?? this._createNoopLogger();\n\n\t\tif (this._autoCleanup) {\n\t\t\tthis._startCleanup();\n\t\t}\n\t}\n\n\t/**\n\t * Create a no-op logger when none is provided.\n\t */\n\tprivate _createNoopLogger(): Logger {\n\t\treturn {\n\t\t\tinfo: (): void => {},\n\t\t\twarn: (): void => {},\n\t\t\terror: (): void => {},\n\t\t\tdebug: (): void => {},\n\t\t\tsetLevel: (): void => {},\n\t\t\tgetLevel: (): 'info' => 'info',\n\t\t};\n\t}\n\n\t/**\n\t * Create a new session.\n\t *\n\t * @returns The session ID\n\t * @throws Error if max sessions reached\n\t */\n\tasync createSession(): Promise<SessionId> {\n\t\twhile (this._createSessionLock) {\n\t\t\tawait this._createSessionLock;\n\t\t}\n\n\t\tif (this._terminated) {\n\t\t\tthrow new PoolTerminatedError();\n\t\t}\n\n\t\tif (this._sessions.size >= this._maxSessions) {\n\t\t\tthrow new MaxSessionsReachedError(this._maxSessions);\n\t\t}\n\n\t\tif (!this._serverFactory) {\n\t\t\tthrow new Error('ConnectionPool requires a serverFactory option to create sessions');\n\t\t}\n\n\t\tlet resolveLock!: () => void;\n\t\tthis._createSessionLock = new Promise<void>((resolve) => {\n\t\t\tresolveLock = resolve;\n\t\t});\n\n\t\ttry {\n\t\t\t// Generate unique session ID\n\t\t\tconst sessionId = asSessionId(\n\t\t\t\t`session_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`\n\t\t\t);\n\n\t\t\t// Create a new server instance for this session\n\t\t\tconst server = await this._serverFactory();\n\t\t\tif (this._terminated) {\n\t\t\t\ttry {\n\t\t\t\t\tawait server.stop();\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthis._pendingCreateStopFailures.add(error);\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\tthrow new PoolTerminatedError();\n\t\t\t}\n\n\t\t\t// Create session\n\t\t\tconst session = new Session(sessionId, server, this._sessionTimeout, this._logger);\n\t\t\tthis._sessions.set(sessionId, session);\n\n\t\t\tthis._logger.info(\n\t\t\t\t`Created session ${sessionId} (${this._sessions.size}/${this._maxSessions} active sessions)`\n\t\t\t);\n\t\t\treturn sessionId;\n\t\t} finally {\n\t\t\tresolveLock();\n\t\t\tthis._createSessionLock = null;\n\t\t}\n\t}\n\n\t/**\n\t * Process a thought in the specified session.\n\t *\n\t * @param sessionId - The session ID\n\t * @param input - The thought data to process\n\t * @returns Promise with the processing result\n\t * @throws Error if session not found\n\t */\n\tasync process(sessionId: SessionId, input: SessionThoughtInput): Promise<ProcessResult> {\n\t\tconst result = await this._runWithSession(sessionId, (session) =>\n\t\t\tsession.processAdmitted(input)\n\t\t);\n\t\tswitch (result.status) {\n\t\t\tcase 'completed':\n\t\t\t\treturn result.value;\n\t\t\tcase 'inactive':\n\t\t\t\tthrow new SessionNotActiveError(sessionId);\n\t\t\tcase 'missing':\n\t\t\t\tthrow new SessionNotFoundError(sessionId);\n\t\t\tdefault:\n\t\t\t\treturn assertNever(result);\n\t\t}\n\t}\n\n\trunWithSession<T>(\n\t\tsessionId: SessionId,\n\t\toperation: (session: SessionServer) => Promise<T>\n\t): Promise<SessionRunResult<T>> {\n\t\treturn this._runWithSession(sessionId, (session) => operation(session.getInfo().server));\n\t}\n\n\tprivate _runWithSession<T>(\n\t\tsessionId: SessionId,\n\t\toperation: (session: Session) => Promise<T>\n\t): Promise<SessionRunResult<T>> {\n\t\tconst activeFrame = this._admissionContext.getStore();\n\t\tif (activeFrame?.open === true && activeFrame.sessionId === sessionId) {\n\t\t\treturn (async () => ({ status: 'completed', value: await operation(activeFrame.session) }))();\n\t\t}\n\n\t\tconst session = this._sessions.get(sessionId);\n\t\tif (!session) {\n\t\t\treturn Promise.resolve(\n\t\t\t\tthis._closingSessions.has(sessionId) ? { status: 'inactive' } : { status: 'missing' }\n\t\t\t);\n\t\t}\n\n\t\treturn session.runWhileActive((capturedSession) => {\n\t\t\tconst frame = { sessionId, session: capturedSession, open: true };\n\t\t\treturn this._admissionContext.run(frame, async () => {\n\t\t\t\ttry {\n\t\t\t\t\treturn await operation(capturedSession);\n\t\t\t\t} finally {\n\t\t\t\t\tframe.open = false;\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t}\n\n\t/**\n\t * Close a session and release resources.\n\t *\n\t * @param sessionId - The session ID to close\n\t * @throws Error if session not found\n\t */\n\tcloseSession(sessionId: SessionId): Promise<void> {\n\t\tconst closing = this._closingSessions.get(sessionId);\n\t\tif (closing) {\n\t\t\treturn closing;\n\t\t}\n\n\t\tconst session = this._sessions.get(sessionId);\n\n\t\tif (!session) {\n\t\t\treturn Promise.reject(new SessionNotFoundError(sessionId));\n\t\t}\n\n\t\tthis._sessions.delete(sessionId);\n\t\tconst closePromise = session.close();\n\t\tthis._closingSessions.set(sessionId, closePromise);\n\t\tvoid closePromise.then(\n\t\t\t() => {\n\t\t\t\tthis._closingSessions.delete(sessionId);\n\t\t\t\tthis._logger.info(\n\t\t\t\t\t`Closed session ${sessionId} (${this._sessions.size}/${this._maxSessions} active sessions)`\n\t\t\t\t);\n\t\t\t},\n\t\t\t() => {\n\t\t\t\tthis._closingSessions.delete(sessionId);\n\t\t\t}\n\t\t);\n\t\treturn closePromise;\n\t}\n\n\t/**\n\t * Get information about a session.\n\t *\n\t * @param sessionId - The session ID\n\t * @returns Session info or undefined if not found\n\t */\n\tgetSessionInfo(sessionId: SessionId): SessionInfo | undefined {\n\t\treturn this._sessions.get(sessionId)?.getInfo();\n\t}\n\n\t/**\n\t * Get all active sessions.\n\t *\n\t * @returns Array of session information\n\t */\n\tgetActiveSessions(): SessionInfo[] {\n\t\treturn Array.from(this._sessions.values())\n\t\t\t.filter((s) => s.isActive)\n\t\t\t.map((s) => s.getInfo());\n\t}\n\n\t/**\n\t * Get connection pool statistics.\n\t */\n\tgetStats(): ConnectionPoolStats {\n\t\tconst activeSessions = this.getActiveSessions();\n\n\t\treturn {\n\t\t\ttotalSessions: this._sessions.size,\n\t\t\tactiveSessions: activeSessions.length,\n\t\t\tmaxSessions: this._maxSessions,\n\t\t\tcleanupEnabled: this._autoCleanup,\n\t\t\tsessionTimeout: this._sessionTimeout,\n\t\t};\n\t}\n\n\t/**\n\t * Start the automatic cleanup timer.\n\t */\n\tprivate _startCleanup(): void {\n\t\tif (this._cleanupTimerId !== null) {\n\t\t\tclearInterval(this._cleanupTimerId);\n\t\t}\n\n\t\tthis._cleanupTimerId = setInterval(() => {\n\t\t\tthis._cleanupTimedOutSessions();\n\t\t}, this._cleanupInterval);\n\t}\n\n\t/**\n\t * Remove timed-out sessions.\n\t */\n\tprivate _cleanupTimedOutSessions(): void {\n\t\tlet cleaned = 0;\n\n\t\tfor (const [sessionId, session] of this._sessions.entries()) {\n\t\t\tif (session.isTimedOut()) {\n\t\t\t\tthis.closeSession(sessionId).catch((err) => {\n\t\t\t\t\tthis._logger.error(`Error closing timed out session ${sessionId}:`, err);\n\t\t\t\t});\n\t\t\t\tcleaned++;\n\t\t\t}\n\t\t}\n\n\t\tif (cleaned > 0) {\n\t\t\tthis._logger.info(\n\t\t\t\t`Cleaned ${cleaned} timed-out sessions (${this._sessions.size}/${this._maxSessions} active sessions)`\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Close all sessions and stop the cleanup timer.\n\t */\n\tterminate(): Promise<void> {\n\t\tif (this._terminatePromise) {\n\t\t\treturn this._terminatePromise;\n\t\t}\n\n\t\tconst completion = Promise.withResolvers<void>();\n\t\tthis._terminatePromise = completion.promise;\n\t\tthis._terminated = true;\n\n\t\t// Stop cleanup timer\n\t\tif (this._cleanupTimerId !== null) {\n\t\t\tclearInterval(this._cleanupTimerId);\n\t\t\tthis._cleanupTimerId = null;\n\t\t}\n\n\t\tconst sessions = Array.from(this._sessions.values());\n\t\tthis._sessions.clear();\n\t\tconst closePromises = new Set<Promise<void>>(this._closingSessions.values());\n\t\tif (this._createSessionLock) {\n\t\t\tclosePromises.add(this._createSessionLock);\n\t\t}\n\t\tfor (const session of sessions) {\n\t\t\tclosePromises.add(session.close());\n\t\t}\n\n\t\tvoid Promise.allSettled(closePromises).then((outcomes) => {\n\t\t\tconst failures = [\n\t\t\t\t...this._pendingCreateStopFailures,\n\t\t\t\t...outcomes\n\t\t\t\t\t.filter((outcome): outcome is PromiseRejectedResult => outcome.status === 'rejected')\n\t\t\t\t\t.map((outcome) => outcome.reason),\n\t\t\t];\n\t\t\tif (failures.length > 0) {\n\t\t\t\tcompletion.reject(new AggregateError(failures, 'ConnectionPool termination failed'));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis._logger.info('ConnectionPool terminated');\n\t\t\tcompletion.resolve();\n\t\t});\n\n\t\treturn completion.promise;\n\t}\n\n\t/**\n\t * Dispose of the connection pool, releasing all resources.\n\t * Implements the IDisposable interface.\n\t * Delegates to terminate() for backward compatibility.\n\t */\n\tdispose(): Promise<void> {\n\t\treturn this.terminate();\n\t}\n\n\t/**\n\t * Check if the connection pool is active.\n\t */\n\tisRunning(): boolean {\n\t\treturn !this._terminated;\n\t}\n}\n\n/**\n * Create a connection pool with the given options.\n *\n * @param options - Connection pool configuration\n * @returns A configured connection pool\n *\n * @example\n * ```typescript\n * const pool = createConnectionPool({\n * maxSessions: 50,\n * sessionTimeout: 300000\n * });\n * ```\n */\nexport function createConnectionPool(options?: SessionOptions): ConnectionPool {\n\treturn new ConnectionPool(options);\n}\n"],"names":["Session","id","server","timeout","logger","Date","input","SessionNotActiveError","operation","Promise","completion","clearTimeout","error","Error","AggregateError","setTimeout","err","ConnectionPool","Map","AsyncLocalStorage","Set","options","PoolTerminatedError","MaxSessionsReachedError","resolveLock","resolve","sessionId","asSessionId","Math","session","result","SessionNotFoundError","assertNever","activeFrame","capturedSession","frame","closing","closePromise","Array","s","activeSessions","clearInterval","setInterval","cleaned","sessions","closePromises","outcomes","failures","outcome","createConnectionPool"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"pool/ConnectionPool.js","sources":["../../src/pool/ConnectionPool.ts"],"sourcesContent":["/**\n * Connection Pool for managing concurrent user sessions.\n *\n * This module provides session management for multi-user scenarios,\n * allowing multiple concurrent clients to each have isolated state.\n *\n * @example\n * ```typescript\n * const pool = new ConnectionPool({\n * maxSessions: 100,\n * sessionTimeout: 300000 // 5 minutes\n * });\n *\n * const sessionId = await pool.createSession();\n * await pool.process(sessionId, thought);\n * await pool.closeSession(sessionId);\n * ```\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport { MaxSessionsReachedError, PoolTerminatedError } from '../errors.js';\nimport type { Logger } from '../logger/StructuredLogger.js';\nimport { asSessionId, type SessionId } from '../contracts/ids.js';\nimport { assertNever } from '../utils.js';\nimport { SessionNotActiveError, SessionNotFoundError } from './PoolErrors.js';\nimport type {\n\tConnectionPoolStats,\n\tIConnectionPool,\n\tProcessResult,\n\tSessionInfo,\n\tSessionRunResult,\n\tSessionServer,\n\tSessionThoughtInput,\n} from './IConnectionPool.js';\n\nexport interface SessionOptions {\n\t/**\n\t * Maximum number of concurrent sessions\n\t * @default 100\n\t */\n\tmaxSessions?: number;\n\n\t/**\n\t * Logger instance\n\t */\n\tlogger?: Logger;\n\n\tserverFactory?: () => Promise<SessionServer>;\n\n\t/**\n\t * Session timeout in milliseconds\n\t * @default 300000 (5 minutes)\n\t */\n\tsessionTimeout?: number;\n\n\t/**\n\t * Whether to enable automatic session cleanup\n\t * @default true\n\t */\n\tautoCleanup?: boolean;\n\n\t/**\n\t * Cleanup interval in milliseconds\n\t * @default 60000 (1 minute)\n\t */\n\tcleanupInterval?: number;\n}\n\n/**\n * Represents a user session with its own server instance.\n */\nexport class Session {\n\tprivate _server: SessionServer;\n\tprivate _id: SessionId;\n\tprivate _createdAt: number;\n\tprivate _lastActivityAt: number;\n\tprivate _isActiveValue: boolean;\n\tprivate _timeout: number;\n\tprivate _cleanupTimer: NodeJS.Timeout | null = null;\n\tprivate _logger: Logger;\n\tprivate _closePromise: Promise<void> | null = null;\n\tprivate _activeOperationCount = 0;\n\tprivate _operationDrain: PromiseWithResolvers<void> | null = null;\n\tprivate readonly _onTimeout: ((sessionId: SessionId) => void) | null;\n\n\tconstructor(\n\t\tid: SessionId,\n\t\tserver: SessionServer,\n\t\ttimeout: number,\n\t\tlogger: Logger,\n\t\tonTimeout: ((sessionId: SessionId) => void) | null = null\n\t) {\n\t\tthis._server = server;\n\t\tthis._id = id;\n\t\tthis._createdAt = Date.now();\n\t\tthis._lastActivityAt = this._createdAt;\n\t\tthis._isActiveValue = true;\n\t\tthis._timeout = timeout;\n\t\tthis._logger = logger;\n\t\tthis._onTimeout = onTimeout;\n\n\t\t// Start session timeout timer\n\t\tthis._startTimeout();\n\t}\n\n\t/**\n\t * Check if the session is active.\n\t */\n\tget isActive(): boolean {\n\t\treturn this._isActiveValue;\n\t}\n\n\t/**\n\t * Process a thought through this session's server instance.\n\t */\n\tasync process(input: SessionThoughtInput): Promise<ProcessResult> {\n\t\tif (!this.isActive) {\n\t\t\tthrow new SessionNotActiveError(this._id);\n\t\t}\n\n\t\t// Update last activity\n\t\tthis._lastActivityAt = Date.now();\n\n\t\t// Reset timeout timer\n\t\tthis._resetTimeout();\n\n\t\t// Process the thought\n\t\treturn this._server.processThought(input);\n\t}\n\n\tprocessAdmitted(input: SessionThoughtInput): Promise<ProcessResult> {\n\t\tthis._lastActivityAt = Date.now();\n\t\tif (this.isActive) this._resetTimeout();\n\t\treturn this._server.processThought(input);\n\t}\n\n\trunWhileActive<T>(operation: (session: Session) => Promise<T>): Promise<SessionRunResult<T>> {\n\t\tif (!this.isActive) return Promise.resolve({ status: 'inactive' });\n\t\tthis._activeOperationCount++;\n\t\treturn (async () => {\n\t\t\ttry {\n\t\t\t\treturn { status: 'completed', value: await operation(this) };\n\t\t\t} finally {\n\t\t\t\tthis._activeOperationCount--;\n\t\t\t\tif (this._activeOperationCount === 0) this._operationDrain?.resolve();\n\t\t\t}\n\t\t})();\n\t}\n\n\t/**\n\t * Get session information.\n\t */\n\tgetInfo(): SessionInfo {\n\t\treturn {\n\t\t\tid: this._id,\n\t\t\tserver: this._server,\n\t\t\tcreatedAt: this._createdAt,\n\t\t\tlastActivityAt: this._lastActivityAt,\n\t\t\tisActive: this.isActive,\n\t\t};\n\t}\n\n\t/**\n\t * Check if the session has timed out.\n\t */\n\tisTimedOut(): boolean {\n\t\treturn Date.now() - this._lastActivityAt > this._timeout;\n\t}\n\n\t/**\n\t * Close the session and stop the server.\n\t */\n\tclose(): Promise<void> {\n\t\tif (this._closePromise) {\n\t\t\treturn this._closePromise;\n\t\t}\n\n\t\tconst completion = Promise.withResolvers<void>();\n\t\tthis._closePromise = completion.promise;\n\t\tvoid completion.promise.then(undefined, () => {\n\t\t\tif (this._closePromise === completion.promise) this._closePromise = null;\n\t\t});\n\t\tthis._isActiveValue = false;\n\n\t\t// Stop timeout timer\n\t\tif (this._cleanupTimer) {\n\t\t\tclearTimeout(this._cleanupTimer);\n\t\t\tthis._cleanupTimer = null;\n\t\t}\n\n\t\tif (this._activeOperationCount === 0) {\n\t\t\tthis._stopServer(completion);\n\t\t} else {\n\t\t\tthis._operationDrain = Promise.withResolvers<void>();\n\t\t\tvoid this._operationDrain.promise.then(() => this._stopServer(completion));\n\t\t}\n\n\t\treturn completion.promise;\n\t}\n\n\tprivate _stopServer(completion: PromiseWithResolvers<void>): void {\n\t\ttry {\n\t\t\tPromise.resolve(this._server.stop()).then(completion.resolve, completion.reject);\n\t\t} catch (error) {\n\t\t\tcompletion.reject(\n\t\t\t\terror instanceof Error ? error : new AggregateError([error], 'Session stop failed')\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Start the session timeout timer.\n\t */\n\tprivate _startTimeout(): void {\n\t\tif (this._cleanupTimer) {\n\t\t\tclearTimeout(this._cleanupTimer);\n\t\t}\n\n\t\tthis._cleanupTimer = setTimeout(() => {\n\t\t\tif (this.isTimedOut()) {\n\t\t\t\tthis._logger.warn(`Session ${this._id} timed out, closing`);\n\t\t\t\tif (this._onTimeout) {\n\t\t\t\t\tthis._onTimeout(this._id);\n\t\t\t\t} else {\n\t\t\t\t\tthis.close().catch((err) => {\n\t\t\t\t\t\tthis._logger.error(`Error closing timed out session ${this._id}:`, err);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}, this._timeout);\n\t}\n\n\t/**\n\t * Reset the timeout timer after activity.\n\t */\n\tprivate _resetTimeout(): void {\n\t\tthis._startTimeout();\n\t}\n}\n\ninterface TerminationGeneration {\n\treadonly attempts: Map<SessionId, Promise<void>>;\n\treadonly completion: PromiseWithResolvers<void>;\n}\n\n/**\n * ConnectionPool manages multiple concurrent user sessions.\n *\n * Each session has its own server instance with isolated state,\n * allowing multiple users to interact with the system simultaneously.\n */\nexport class ConnectionPool implements IConnectionPool {\n\tprivate _sessions: Map<SessionId, Session> = new Map();\n\tprivate readonly _ownedSessions = new Map<SessionId, Session>();\n\tprivate readonly _closingSessions = new Map<SessionId, Promise<void>>();\n\tprivate _createSessionLock: Promise<void> | null = null;\n\tprivate _maxSessions: number;\n\tprivate _sessionTimeout: number;\n\tprivate _autoCleanup: boolean;\n\tprivate _cleanupInterval: number;\n\tprivate _cleanupTimerId: ReturnType<typeof setInterval> | null = null;\n\tprivate _terminated: boolean = false;\n\tprivate _logger: Logger;\n\tprivate _serverFactory: (() => Promise<SessionServer>) | null;\n\tprivate _terminationGeneration: TerminationGeneration | null = null;\n\tprivate readonly _admissionContext = new AsyncLocalStorage<{\n\t\treadonly sessionId: SessionId;\n\t\treadonly session: Session;\n\t\topen: boolean;\n\t}>();\n\n\tconstructor(options: SessionOptions = {}) {\n\t\tthis._maxSessions = options.maxSessions ?? 100;\n\t\tthis._sessionTimeout = options.sessionTimeout ?? 300000; // 5 minutes\n\t\tthis._autoCleanup = options.autoCleanup ?? true;\n\t\tthis._cleanupInterval = options.cleanupInterval ?? 60000; // 1 minute\n\t\tthis._serverFactory = options.serverFactory ?? null;\n\t\tthis._logger = options.logger ?? this._createNoopLogger();\n\n\t\tif (this._autoCleanup) {\n\t\t\tthis._startCleanup();\n\t\t}\n\t}\n\n\t/**\n\t * Create a no-op logger when none is provided.\n\t */\n\tprivate _createNoopLogger(): Logger {\n\t\treturn {\n\t\t\tinfo: (): void => {},\n\t\t\twarn: (): void => {},\n\t\t\terror: (): void => {},\n\t\t\tdebug: (): void => {},\n\t\t\tsetLevel: (): void => {},\n\t\t\tgetLevel: (): 'info' => 'info',\n\t\t};\n\t}\n\n\t/**\n\t * Create a new session.\n\t *\n\t * @returns The session ID\n\t * @throws Error if max sessions reached\n\t */\n\tasync createSession(): Promise<SessionId> {\n\t\twhile (this._createSessionLock) {\n\t\t\tawait this._createSessionLock;\n\t\t}\n\n\t\tif (this._terminated) {\n\t\t\tthrow new PoolTerminatedError();\n\t\t}\n\n\t\tif (this._ownedSessions.size >= this._maxSessions) {\n\t\t\tthrow new MaxSessionsReachedError(this._maxSessions);\n\t\t}\n\n\t\tif (!this._serverFactory) {\n\t\t\tthrow new Error('ConnectionPool requires a serverFactory option to create sessions');\n\t\t}\n\n\t\tlet resolveLock!: () => void;\n\t\tthis._createSessionLock = new Promise<void>((resolve) => {\n\t\t\tresolveLock = resolve;\n\t\t});\n\n\t\ttry {\n\t\t\t// Generate unique session ID\n\t\t\tconst sessionId = asSessionId(\n\t\t\t\t`session_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`\n\t\t\t);\n\t\t\tif (this._ownedSessions.has(sessionId)) {\n\t\t\t\tthrow new Error(`Session ID collision: ${sessionId}`);\n\t\t\t}\n\n\t\t\t// Create a new server instance for this session\n\t\t\tconst server = await this._serverFactory();\n\t\t\tconst session = new Session(\n\t\t\t\tsessionId,\n\t\t\t\tserver,\n\t\t\t\tthis._sessionTimeout,\n\t\t\t\tthis._logger,\n\t\t\t\t(timedOutSessionId) => this._handleSessionTimeout(timedOutSessionId)\n\t\t\t);\n\t\t\tthis._ownedSessions.set(sessionId, session);\n\t\t\tif (this._terminated) {\n\t\t\t\tconst generation = this._terminationGeneration;\n\t\t\t\tif (generation) {\n\t\t\t\t\tawait this._coordinateCleanup(sessionId, generation);\n\t\t\t\t} else {\n\t\t\t\t\tawait this._coordinateCleanup(sessionId);\n\t\t\t\t}\n\t\t\t\tthrow new PoolTerminatedError();\n\t\t\t}\n\n\t\t\tthis._sessions.set(sessionId, session);\n\n\t\t\tthis._logger.info(\n\t\t\t\t`Created session ${sessionId} (${this._sessions.size}/${this._maxSessions} active sessions)`\n\t\t\t);\n\t\t\treturn sessionId;\n\t\t} finally {\n\t\t\tresolveLock();\n\t\t\tthis._createSessionLock = null;\n\t\t}\n\t}\n\n\t/**\n\t * Process a thought in the specified session.\n\t *\n\t * @param sessionId - The session ID\n\t * @param input - The thought data to process\n\t * @returns Promise with the processing result\n\t * @throws Error if session not found\n\t */\n\tasync process(sessionId: SessionId, input: SessionThoughtInput): Promise<ProcessResult> {\n\t\tconst result = await this._runWithSession(sessionId, (session) =>\n\t\t\tsession.processAdmitted(input)\n\t\t);\n\t\tswitch (result.status) {\n\t\t\tcase 'completed':\n\t\t\t\treturn result.value;\n\t\t\tcase 'inactive':\n\t\t\t\tthrow new SessionNotActiveError(sessionId);\n\t\t\tcase 'missing':\n\t\t\t\tthrow new SessionNotFoundError(sessionId);\n\t\t\tdefault:\n\t\t\t\treturn assertNever(result);\n\t\t}\n\t}\n\n\trunWithSession<T>(\n\t\tsessionId: SessionId,\n\t\toperation: (session: SessionServer) => Promise<T>\n\t): Promise<SessionRunResult<T>> {\n\t\treturn this._runWithSession(sessionId, (session) => operation(session.getInfo().server));\n\t}\n\n\tprivate _runWithSession<T>(\n\t\tsessionId: SessionId,\n\t\toperation: (session: Session) => Promise<T>\n\t): Promise<SessionRunResult<T>> {\n\t\tconst activeFrame = this._admissionContext.getStore();\n\t\tif (activeFrame?.open === true && activeFrame.sessionId === sessionId) {\n\t\t\treturn (async () => ({ status: 'completed', value: await operation(activeFrame.session) }))();\n\t\t}\n\n\t\tconst session = this._sessions.get(sessionId);\n\t\tif (!session) {\n\t\t\treturn Promise.resolve(\n\t\t\t\tthis._ownedSessions.has(sessionId) ? { status: 'inactive' } : { status: 'missing' }\n\t\t\t);\n\t\t}\n\n\t\treturn session.runWhileActive((capturedSession) => {\n\t\t\tconst frame = { sessionId, session: capturedSession, open: true };\n\t\t\treturn this._admissionContext.run(frame, async () => {\n\t\t\t\ttry {\n\t\t\t\t\treturn await operation(capturedSession);\n\t\t\t\t} finally {\n\t\t\t\t\tframe.open = false;\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t}\n\n\t/**\n\t * Close a session and release resources.\n\t *\n\t * @param sessionId - The session ID to close\n\t * @throws Error if session not found\n\t */\n\tcloseSession(sessionId: SessionId): Promise<void> {\n\t\tif (!this._ownedSessions.has(sessionId)) {\n\t\t\treturn Promise.reject(new SessionNotFoundError(sessionId));\n\t\t}\n\n\t\tconst generation = this._terminationGeneration;\n\t\treturn generation\n\t\t\t? this._coordinateCleanup(sessionId, generation)\n\t\t\t: this._coordinateCleanup(sessionId);\n\t}\n\n\tprivate _coordinateCleanup(\n\t\tsessionId: SessionId,\n\t\tgeneration?: TerminationGeneration\n\t): Promise<void> {\n\t\tconst generationAttempt = generation?.attempts.get(sessionId);\n\t\tif (generationAttempt) return generationAttempt;\n\n\t\tconst session = this._ownedSessions.get(sessionId);\n\t\tif (!session) return Promise.reject(new SessionNotFoundError(sessionId));\n\n\t\tthis._sessions.delete(sessionId);\n\t\tconst currentAttempt = this._closingSessions.get(sessionId);\n\t\tconst attempt = currentAttempt ?? session.close();\n\t\tif (!currentAttempt) this._closingSessions.set(sessionId, attempt);\n\t\tgeneration?.attempts.set(sessionId, attempt);\n\n\t\tif (!currentAttempt)\n\t\t\tvoid attempt.then(\n\t\t\t\t() => {\n\t\t\t\t\tif (this._closingSessions.get(sessionId) === attempt) {\n\t\t\t\t\t\tthis._closingSessions.delete(sessionId);\n\t\t\t\t\t}\n\t\t\t\t\tif (this._ownedSessions.get(sessionId) === session) {\n\t\t\t\t\t\tthis._ownedSessions.delete(sessionId);\n\t\t\t\t\t}\n\t\t\t\t\tthis._logger.info(\n\t\t\t\t\t\t`Closed session ${sessionId} (${this._sessions.size}/${this._maxSessions} active sessions)`\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\tif (this._closingSessions.get(sessionId) === attempt) {\n\t\t\t\t\t\tthis._closingSessions.delete(sessionId);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t);\n\t\treturn attempt;\n\t}\n\n\tprivate _handleSessionTimeout(sessionId: SessionId): void {\n\t\tconst generation = this._terminationGeneration;\n\t\tconst attempt = generation\n\t\t\t? this._coordinateCleanup(sessionId, generation)\n\t\t\t: this._coordinateCleanup(sessionId);\n\t\tvoid attempt.catch((err) => {\n\t\t\tthis._logger.error(`Error closing timed out session ${sessionId}:`, err);\n\t\t});\n\t}\n\n\t/**\n\t * Get information about a session.\n\t *\n\t * @param sessionId - The session ID\n\t * @returns Session info or undefined if not found\n\t */\n\tgetSessionInfo(sessionId: SessionId): SessionInfo | undefined {\n\t\treturn this._sessions.get(sessionId)?.getInfo();\n\t}\n\n\t/**\n\t * Get all active sessions.\n\t *\n\t * @returns Array of session information\n\t */\n\tgetActiveSessions(): SessionInfo[] {\n\t\treturn Array.from(this._sessions.values())\n\t\t\t.filter((s) => s.isActive)\n\t\t\t.map((s) => s.getInfo());\n\t}\n\n\t/**\n\t * Get connection pool statistics.\n\t */\n\tgetStats(): ConnectionPoolStats {\n\t\tconst activeSessions = this.getActiveSessions();\n\n\t\treturn {\n\t\t\ttotalSessions: this._sessions.size,\n\t\t\tactiveSessions: activeSessions.length,\n\t\t\tmaxSessions: this._maxSessions,\n\t\t\tcleanupEnabled: this._autoCleanup,\n\t\t\tsessionTimeout: this._sessionTimeout,\n\t\t};\n\t}\n\n\t/**\n\t * Start the automatic cleanup timer.\n\t */\n\tprivate _startCleanup(): void {\n\t\tif (this._cleanupTimerId !== null) {\n\t\t\tclearInterval(this._cleanupTimerId);\n\t\t}\n\n\t\tthis._cleanupTimerId = setInterval(() => {\n\t\t\tthis._cleanupTimedOutSessions();\n\t\t}, this._cleanupInterval);\n\t}\n\n\t/**\n\t * Remove timed-out sessions.\n\t */\n\tprivate _cleanupTimedOutSessions(): void {\n\t\tlet cleaned = 0;\n\n\t\tfor (const [sessionId, session] of this._ownedSessions.entries()) {\n\t\t\tif (session.isTimedOut()) {\n\t\t\t\tthis._coordinateCleanup(sessionId).catch((err) => {\n\t\t\t\t\tthis._logger.error(`Error closing timed out session ${sessionId}:`, err);\n\t\t\t\t});\n\t\t\t\tcleaned++;\n\t\t\t}\n\t\t}\n\n\t\tif (cleaned > 0) {\n\t\t\tthis._logger.info(\n\t\t\t\t`Cleaned ${cleaned} timed-out sessions (${this._sessions.size}/${this._maxSessions} active sessions)`\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Close all sessions and stop the cleanup timer.\n\t */\n\tterminate(): Promise<void> {\n\t\tif (this._terminationGeneration) {\n\t\t\treturn this._terminationGeneration.completion.promise;\n\t\t}\n\n\t\tconst completion = Promise.withResolvers<void>();\n\t\tconst generation: TerminationGeneration = { attempts: new Map(), completion };\n\t\tthis._terminationGeneration = generation;\n\t\tthis._terminated = true;\n\n\t\t// Stop cleanup timer\n\t\tif (this._cleanupTimerId !== null) {\n\t\t\tclearInterval(this._cleanupTimerId);\n\t\t\tthis._cleanupTimerId = null;\n\t\t}\n\n\t\tconst pendingCreate = this._createSessionLock;\n\t\tfor (const sessionId of this._ownedSessions.keys()) {\n\t\t\tthis._coordinateCleanup(sessionId, generation);\n\t\t}\n\n\t\tvoid this._completeTerminationGeneration(generation, pendingCreate);\n\n\t\treturn completion.promise;\n\t}\n\n\tprivate async _completeTerminationGeneration(\n\t\tgeneration: TerminationGeneration,\n\t\tpendingCreate: Promise<void> | null\n\t): Promise<void> {\n\t\tif (pendingCreate) await pendingCreate;\n\t\tconst outcomes = await Promise.allSettled(generation.attempts.values());\n\t\tconst failures = outcomes\n\t\t\t.filter((outcome): outcome is PromiseRejectedResult => outcome.status === 'rejected')\n\t\t\t.map((outcome) => outcome.reason);\n\t\tif (failures.length > 0) {\n\t\t\tif (this._terminationGeneration === generation) this._terminationGeneration = null;\n\t\t\tgeneration.completion.reject(\n\t\t\t\tnew AggregateError(failures, 'ConnectionPool termination failed')\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tthis._logger.info('ConnectionPool terminated');\n\t\tgeneration.completion.resolve();\n\t}\n\n\t/**\n\t * Dispose of the connection pool, releasing all resources.\n\t * Implements the IDisposable interface.\n\t * Delegates to terminate() for backward compatibility.\n\t */\n\tdispose(): Promise<void> {\n\t\treturn this.terminate();\n\t}\n\n\t/**\n\t * Check if the connection pool is active.\n\t */\n\tisRunning(): boolean {\n\t\treturn !this._terminated;\n\t}\n}\n\n/**\n * Create a connection pool with the given options.\n *\n * @param options - Connection pool configuration\n * @returns A configured connection pool\n *\n * @example\n * ```typescript\n * const pool = createConnectionPool({\n * maxSessions: 50,\n * sessionTimeout: 300000\n * });\n * ```\n */\nexport function createConnectionPool(options?: SessionOptions): ConnectionPool {\n\treturn new ConnectionPool(options);\n}\n"],"names":["Session","id","server","timeout","logger","onTimeout","Date","input","SessionNotActiveError","operation","Promise","completion","undefined","clearTimeout","error","Error","AggregateError","setTimeout","err","ConnectionPool","Map","AsyncLocalStorage","options","PoolTerminatedError","MaxSessionsReachedError","resolveLock","resolve","sessionId","asSessionId","Math","session","timedOutSessionId","generation","result","SessionNotFoundError","assertNever","activeFrame","capturedSession","frame","generationAttempt","currentAttempt","attempt","Array","s","activeSessions","clearInterval","setInterval","cleaned","pendingCreate","outcomes","failures","outcome","createConnectionPool"],"mappings":"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"}
|
|
@@ -69,6 +69,12 @@ export interface StreamableHttpTransportOptions extends TransportOptions {
|
|
|
69
69
|
* @default 30000 (30 seconds)
|
|
70
70
|
*/
|
|
71
71
|
requestTimeout?: number;
|
|
72
|
+
/** Maximum retained sessions when idle expiry is enabled. */
|
|
73
|
+
maxSessions?: number;
|
|
74
|
+
/** Idle duration before an unpinned session expires. */
|
|
75
|
+
sessionIdleTimeoutMs?: number;
|
|
76
|
+
/** Interval between idle-session sweeps. */
|
|
77
|
+
sessionSweepIntervalMs?: number;
|
|
72
78
|
}
|
|
73
79
|
/**
|
|
74
80
|
* Streamable HTTP Transport for MCP server.
|
|
@@ -122,6 +128,8 @@ export declare class StreamableHttpTransport extends BaseTransport implements IT
|
|
|
122
128
|
private _metricsProvider;
|
|
123
129
|
private readonly _lifecycleFailureReporter;
|
|
124
130
|
private readonly _acceptedWork;
|
|
131
|
+
private readonly _sessionRetention;
|
|
132
|
+
private _sessionSweepIntervalId;
|
|
125
133
|
private _stopPromise;
|
|
126
134
|
constructor(options?: StreamableHttpTransportOptions);
|
|
127
135
|
private _requireMcpServer;
|
|
@@ -155,6 +163,7 @@ export declare class StreamableHttpTransport extends BaseTransport implements IT
|
|
|
155
163
|
* - Content-Type: application/json for responses
|
|
156
164
|
*/
|
|
157
165
|
private _handleMcpPost;
|
|
166
|
+
private _acceptPostSession;
|
|
158
167
|
private _receiveAndFinalize;
|
|
159
168
|
/**
|
|
160
169
|
* Handle GET /mcp — Optional SSE notification stream.
|
|
@@ -170,6 +179,9 @@ export declare class StreamableHttpTransport extends BaseTransport implements IT
|
|
|
170
179
|
* @returns Session ID string on success, or the response error to finalize.
|
|
171
180
|
*/
|
|
172
181
|
private _resolveSession;
|
|
182
|
+
private _sweepExpiredSessions;
|
|
183
|
+
private _disposeSession;
|
|
184
|
+
private _stopSessionSweep;
|
|
173
185
|
/**
|
|
174
186
|
* Extract Mcp-Session-Id from request headers.
|
|
175
187
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StreamableHttpTransport.d.ts","sourceRoot":"","sources":["../../src/transport/StreamableHttpTransport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAEtC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAG3D,OAAO,EAAE,aAAa,EAAE,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAU3E;;GAEG;AACH,MAAM,WAAW,8BAA+B,SAAQ,gBAAgB;IACvE;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,OAAO,CAAC,EAAE,QAAQ,CAAC;IAEnB;;OAEG;IACH,eAAe,CAAC,EAAE,MAAM,MAAM,CAAC;IAE/B;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,MAAM,CAAC;IAElC;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"StreamableHttpTransport.d.ts","sourceRoot":"","sources":["../../src/transport/StreamableHttpTransport.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,6 +8,16 @@ import { asSessionId } from "../contracts/ids.js";
|
|
|
8
8
|
import { readRequestBody } from "./HttpHelpers.js";
|
|
9
9
|
import { runWithContext } from "../context/RequestContext.js";
|
|
10
10
|
import { AcceptedWorkTracker, LifecycleFailureReporter, ResponseFinalizer } from "./HttpRequestLifecycle.js";
|
|
11
|
+
function parseSessionRetentionOptions(options) {
|
|
12
|
+
const { maxSessions, sessionIdleTimeoutMs, sessionSweepIntervalMs } = options;
|
|
13
|
+
if (void 0 === maxSessions && void 0 === sessionIdleTimeoutMs && void 0 === sessionSweepIntervalMs) return null;
|
|
14
|
+
if (void 0 === maxSessions || !Number.isFinite(maxSessions) || !Number.isInteger(maxSessions) || maxSessions <= 0 || void 0 === sessionIdleTimeoutMs || !Number.isFinite(sessionIdleTimeoutMs) || !Number.isInteger(sessionIdleTimeoutMs) || sessionIdleTimeoutMs <= 0 || void 0 === sessionSweepIntervalMs || !Number.isFinite(sessionSweepIntervalMs) || !Number.isInteger(sessionSweepIntervalMs) || sessionSweepIntervalMs <= 0) throw new TypeError('maxSessions, sessionIdleTimeoutMs, and sessionSweepIntervalMs must be provided together as positive integers');
|
|
15
|
+
return {
|
|
16
|
+
maxSessions,
|
|
17
|
+
sessionIdleTimeoutMs,
|
|
18
|
+
sessionSweepIntervalMs
|
|
19
|
+
};
|
|
20
|
+
}
|
|
11
21
|
class StreamableHttpTransport extends BaseTransport {
|
|
12
22
|
get kind() {
|
|
13
23
|
return 'streamable-http';
|
|
@@ -27,8 +37,11 @@ class StreamableHttpTransport extends BaseTransport {
|
|
|
27
37
|
_metricsProvider;
|
|
28
38
|
_lifecycleFailureReporter;
|
|
29
39
|
_acceptedWork;
|
|
40
|
+
_sessionRetention;
|
|
41
|
+
_sessionSweepIntervalId = null;
|
|
30
42
|
_stopPromise = null;
|
|
31
43
|
constructor(options = {}){
|
|
44
|
+
const sessionRetention = parseSessionRetentionOptions(options);
|
|
32
45
|
super(options);
|
|
33
46
|
this._path = options.path ?? '/mcp';
|
|
34
47
|
this._stateful = options.stateful ?? true;
|
|
@@ -38,12 +51,17 @@ class StreamableHttpTransport extends BaseTransport {
|
|
|
38
51
|
this._requestTimeout = options.requestTimeout ?? 30000;
|
|
39
52
|
this._metrics = options.metrics;
|
|
40
53
|
this._metricsProvider = options.metricsProvider ?? null;
|
|
54
|
+
this._sessionRetention = sessionRetention;
|
|
41
55
|
this._lifecycleFailureReporter = new LifecycleFailureReporter((error)=>{
|
|
42
56
|
this.log('error', 'Streamable HTTP request lifecycle failed', {
|
|
43
57
|
error: getErrorMessage(error)
|
|
44
58
|
});
|
|
45
59
|
});
|
|
46
60
|
this._acceptedWork = new AcceptedWorkTracker(this._lifecycleFailureReporter);
|
|
61
|
+
if (this._stateful && sessionRetention) {
|
|
62
|
+
this._sessionSweepIntervalId = setInterval(()=>this._sweepExpiredSessions(), sessionRetention.sessionSweepIntervalMs);
|
|
63
|
+
this._sessionSweepIntervalId.unref();
|
|
64
|
+
}
|
|
47
65
|
}
|
|
48
66
|
_requireMcpServer() {
|
|
49
67
|
if (!this._mcpServer) throw new Error('MCP server not initialized. Did you call connect()?');
|
|
@@ -158,6 +176,7 @@ class StreamableHttpTransport extends BaseTransport {
|
|
|
158
176
|
this._requestCount++;
|
|
159
177
|
this._activeRequests++;
|
|
160
178
|
const responseFinalizer = new ResponseFinalizer(res, this._lifecycleFailureReporter);
|
|
179
|
+
let acceptedSession = null;
|
|
161
180
|
const timeout = setTimeout(()=>{
|
|
162
181
|
responseFinalizer.finalize((response)=>{
|
|
163
182
|
this._sendJsonRpcError(response, 500, -32603, 'Request timeout');
|
|
@@ -185,18 +204,13 @@ class StreamableHttpTransport extends BaseTransport {
|
|
|
185
204
|
});
|
|
186
205
|
});
|
|
187
206
|
const jsonRpcRequest = parseResult.output;
|
|
188
|
-
|
|
189
|
-
if (
|
|
190
|
-
|
|
191
|
-
if ('string' != typeof sessionResult) return void responseFinalizer.finalize((response)=>{
|
|
192
|
-
this._sendJsonRpcError(response, sessionResult.statusCode, sessionResult.code, sessionResult.message);
|
|
193
|
-
});
|
|
194
|
-
sessionId = sessionResult;
|
|
195
|
-
}
|
|
207
|
+
const session = this._acceptPostSession(req, responseFinalizer);
|
|
208
|
+
if (!session) return;
|
|
209
|
+
acceptedSession = session.state;
|
|
196
210
|
if (!this._mcpServer) return void responseFinalizer.finalize((response)=>{
|
|
197
211
|
this._sendJsonRpcError(response, 503, -32603, 'Server not ready', jsonRpcRequest?.id ?? null);
|
|
198
212
|
});
|
|
199
|
-
await this._receiveAndFinalize(jsonRpcRequest,
|
|
213
|
+
await this._receiveAndFinalize(jsonRpcRequest, session.id, responseFinalizer);
|
|
200
214
|
} catch (error) {
|
|
201
215
|
responseFinalizer.finalize((response)=>{
|
|
202
216
|
this._sendJsonRpcError(response, 200, -32603, 'Internal error', null, {
|
|
@@ -204,10 +218,35 @@ class StreamableHttpTransport extends BaseTransport {
|
|
|
204
218
|
});
|
|
205
219
|
});
|
|
206
220
|
} finally{
|
|
221
|
+
if (acceptedSession) acceptedSession.acceptedPostCount--;
|
|
207
222
|
clearTimeout(timeout);
|
|
208
223
|
this._activeRequests--;
|
|
209
224
|
}
|
|
210
225
|
}
|
|
226
|
+
_acceptPostSession(req, responseFinalizer) {
|
|
227
|
+
if (!this._stateful) return {
|
|
228
|
+
id: void 0,
|
|
229
|
+
state: null
|
|
230
|
+
};
|
|
231
|
+
if (this.isShuttingDown) {
|
|
232
|
+
responseFinalizer.finalize((response)=>{
|
|
233
|
+
this._sendJsonRpcError(response, 503, -32603, 'Server is shutting down');
|
|
234
|
+
});
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
const session = this._resolveSession(req);
|
|
238
|
+
if (!('state' in session)) {
|
|
239
|
+
responseFinalizer.finalize((response)=>{
|
|
240
|
+
this._sendJsonRpcError(response, session.statusCode, session.code, session.message);
|
|
241
|
+
});
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
session.state.acceptedPostCount++;
|
|
245
|
+
return {
|
|
246
|
+
id: session.id,
|
|
247
|
+
state: session.state
|
|
248
|
+
};
|
|
249
|
+
}
|
|
211
250
|
async _receiveAndFinalize(jsonRpcRequest, sessionId, responseFinalizer) {
|
|
212
251
|
const owner = this._stateful ? sessionId ?? randomUUID() : randomUUID();
|
|
213
252
|
const response = await runWithContext({
|
|
@@ -277,6 +316,7 @@ class StreamableHttpTransport extends BaseTransport {
|
|
|
277
316
|
}));
|
|
278
317
|
return;
|
|
279
318
|
}
|
|
319
|
+
session.lastActivityAt = Date.now();
|
|
280
320
|
res.writeHead(200, {
|
|
281
321
|
'Content-Type': 'text/event-stream',
|
|
282
322
|
'Cache-Control': 'no-cache',
|
|
@@ -288,7 +328,6 @@ class StreamableHttpTransport extends BaseTransport {
|
|
|
288
328
|
timestamp: Date.now()
|
|
289
329
|
});
|
|
290
330
|
session.notificationStreams.add(res);
|
|
291
|
-
session.lastActivityAt = Date.now();
|
|
292
331
|
this._updateSessionMetrics();
|
|
293
332
|
req.on('close', ()=>{
|
|
294
333
|
session.notificationStreams.delete(res);
|
|
@@ -306,7 +345,10 @@ class StreamableHttpTransport extends BaseTransport {
|
|
|
306
345
|
const session = this._sessions.get(asSessionId(headerSessionId));
|
|
307
346
|
if (session) {
|
|
308
347
|
session.lastActivityAt = Date.now();
|
|
309
|
-
return
|
|
348
|
+
return {
|
|
349
|
+
id: session.id,
|
|
350
|
+
state: session
|
|
351
|
+
};
|
|
310
352
|
}
|
|
311
353
|
return {
|
|
312
354
|
statusCode: 404,
|
|
@@ -314,17 +356,51 @@ class StreamableHttpTransport extends BaseTransport {
|
|
|
314
356
|
message: 'Session not found'
|
|
315
357
|
};
|
|
316
358
|
}
|
|
317
|
-
|
|
359
|
+
if (this._sessionRetention && this._sessions.size >= this._sessionRetention.maxSessions) return {
|
|
360
|
+
statusCode: 503,
|
|
361
|
+
code: -32000,
|
|
362
|
+
message: 'Session capacity reached'
|
|
363
|
+
};
|
|
364
|
+
const newSessionId = asSessionId(this._sessionIdGenerator());
|
|
365
|
+
const now = Date.now();
|
|
318
366
|
const sessionState = {
|
|
319
367
|
id: newSessionId,
|
|
320
|
-
createdAt:
|
|
321
|
-
lastActivityAt:
|
|
322
|
-
notificationStreams: new Set()
|
|
368
|
+
createdAt: now,
|
|
369
|
+
lastActivityAt: now,
|
|
370
|
+
notificationStreams: new Set(),
|
|
371
|
+
acceptedPostCount: 0
|
|
323
372
|
};
|
|
324
|
-
this._sessions.set(
|
|
373
|
+
this._sessions.set(newSessionId, sessionState);
|
|
325
374
|
this.log('info', `New session created: ${newSessionId}`);
|
|
326
375
|
this._updateSessionMetrics();
|
|
327
|
-
return
|
|
376
|
+
return {
|
|
377
|
+
id: newSessionId,
|
|
378
|
+
state: sessionState
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
_sweepExpiredSessions() {
|
|
382
|
+
if (!this._sessionRetention) return;
|
|
383
|
+
const now = Date.now();
|
|
384
|
+
for (const [sessionId, session] of this._sessions)if (0 === session.acceptedPostCount && now - session.lastActivityAt >= this._sessionRetention.sessionIdleTimeoutMs) this._disposeSession(sessionId);
|
|
385
|
+
}
|
|
386
|
+
_disposeSession(sessionId) {
|
|
387
|
+
const session = this._sessions.get(sessionId);
|
|
388
|
+
if (!session) return;
|
|
389
|
+
this._sessions.delete(sessionId);
|
|
390
|
+
for (const stream of session.notificationStreams)try {
|
|
391
|
+
stream.end();
|
|
392
|
+
} catch (error) {
|
|
393
|
+
this._lifecycleFailureReporter.report(error);
|
|
394
|
+
}
|
|
395
|
+
session.notificationStreams.clear();
|
|
396
|
+
this._updateSessionMetrics();
|
|
397
|
+
}
|
|
398
|
+
_stopSessionSweep() {
|
|
399
|
+
const interval = this._sessionSweepIntervalId;
|
|
400
|
+
if (!interval) return;
|
|
401
|
+
interval.unref();
|
|
402
|
+
clearInterval(interval);
|
|
403
|
+
this._sessionSweepIntervalId = null;
|
|
328
404
|
}
|
|
329
405
|
_getSessionIdFromHeader(req) {
|
|
330
406
|
const value = req.headers['mcp-session-id'];
|
|
@@ -399,14 +475,9 @@ class StreamableHttpTransport extends BaseTransport {
|
|
|
399
475
|
if (this._stopPromise) return this._stopPromise;
|
|
400
476
|
this._isShuttingDown = true;
|
|
401
477
|
this._stopRateLimitCleanup();
|
|
478
|
+
this._stopSessionSweep();
|
|
402
479
|
const shutdownTimeout = timeout ?? 30000;
|
|
403
|
-
for (const
|
|
404
|
-
for (const stream of session.notificationStreams)try {
|
|
405
|
-
stream.end();
|
|
406
|
-
} catch {}
|
|
407
|
-
session.notificationStreams.clear();
|
|
408
|
-
}
|
|
409
|
-
this._sessions.clear();
|
|
480
|
+
for (const sessionId of this._sessions.keys())this._disposeSession(sessionId);
|
|
410
481
|
const server = this._server;
|
|
411
482
|
const serverClosed = new Promise((resolve, reject)=>{
|
|
412
483
|
if (!server?.listening) return void resolve();
|