@iworkforces/tracelattice 2.0.7 → 2.1.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.
Files changed (217) hide show
  1. package/README.md +88 -64
  2. package/dist/ServerConfig.d.ts +1 -1
  3. package/dist/ServerConfig.js.map +1 -1
  4. package/dist/cli.js +223 -254
  5. package/dist/cli.js.map +1 -1
  6. package/dist/config/ConfigLoader.d.ts +29 -26
  7. package/dist/config/ConfigLoader.d.ts.map +1 -1
  8. package/dist/config/ConfigLoader.js +40 -36
  9. package/dist/config/ConfigLoader.js.map +1 -1
  10. package/dist/contracts/PersistenceBackend.d.ts +7 -108
  11. package/dist/contracts/PersistenceBackend.d.ts.map +1 -1
  12. package/dist/contracts/PersistenceBackend.js +1 -6
  13. package/dist/contracts/calibrator.d.ts +6 -6
  14. package/dist/contracts/calibrator.d.ts.map +1 -1
  15. package/dist/contracts/ids.d.ts +1 -8
  16. package/dist/contracts/ids.d.ts.map +1 -1
  17. package/dist/contracts/ids.js +2 -3
  18. package/dist/contracts/ids.js.map +1 -1
  19. package/dist/contracts/interfaces.d.ts +4 -6
  20. package/dist/contracts/interfaces.d.ts.map +1 -1
  21. package/dist/contracts/persistence-work.d.ts +2 -3
  22. package/dist/contracts/persistence-work.d.ts.map +1 -1
  23. package/dist/contracts/summary.d.ts +0 -1
  24. package/dist/contracts/summary.d.ts.map +1 -1
  25. package/dist/core/HistoryManager.d.ts +12 -16
  26. package/dist/core/HistoryManager.d.ts.map +1 -1
  27. package/dist/core/HistoryManager.js +19 -40
  28. package/dist/core/HistoryManager.js.map +1 -1
  29. package/dist/core/IHistoryManager.d.ts +22 -30
  30. package/dist/core/IHistoryManager.d.ts.map +1 -1
  31. package/dist/core/InputNormalizer.d.ts +7 -24
  32. package/dist/core/InputNormalizer.d.ts.map +1 -1
  33. package/dist/core/InputNormalizer.js +3 -9
  34. package/dist/core/InputNormalizer.js.map +1 -1
  35. package/dist/core/PersistenceBuffer.d.ts +3 -20
  36. package/dist/core/PersistenceBuffer.d.ts.map +1 -1
  37. package/dist/core/PersistenceBuffer.js +1 -4
  38. package/dist/core/PersistenceBuffer.js.map +1 -1
  39. package/dist/core/PersistenceRestore.d.ts +1 -1
  40. package/dist/core/PersistenceRestore.d.ts.map +1 -1
  41. package/dist/core/PersistenceRestore.js +4 -10
  42. package/dist/core/PersistenceRestore.js.map +1 -1
  43. package/dist/core/PersistenceWriter.d.ts.map +1 -1
  44. package/dist/core/PersistenceWriter.js +3 -12
  45. package/dist/core/PersistenceWriter.js.map +1 -1
  46. package/dist/core/SessionErrors.d.ts +0 -11
  47. package/dist/core/SessionErrors.d.ts.map +1 -1
  48. package/dist/core/SessionErrors.js +1 -14
  49. package/dist/core/SessionErrors.js.map +1 -1
  50. package/dist/core/SessionLock.d.ts +4 -5
  51. package/dist/core/SessionLock.d.ts.map +1 -1
  52. package/dist/core/SessionLock.js +5 -10
  53. package/dist/core/SessionLock.js.map +1 -1
  54. package/dist/core/SessionManager.d.ts +0 -2
  55. package/dist/core/SessionManager.d.ts.map +1 -1
  56. package/dist/core/SessionManager.js +3 -5
  57. package/dist/core/SessionManager.js.map +1 -1
  58. package/dist/core/SessionResetCoordinator.d.ts +10 -7
  59. package/dist/core/SessionResetCoordinator.d.ts.map +1 -1
  60. package/dist/core/SessionResetCoordinator.js +10 -28
  61. package/dist/core/SessionResetCoordinator.js.map +1 -1
  62. package/dist/core/ThoughtEvaluator.d.ts +5 -5
  63. package/dist/core/ThoughtEvaluator.d.ts.map +1 -1
  64. package/dist/core/ThoughtEvaluator.js +4 -40
  65. package/dist/core/ThoughtEvaluator.js.map +1 -1
  66. package/dist/core/ThoughtFormatter.js.map +1 -1
  67. package/dist/core/ThoughtProcessor.d.ts +10 -3
  68. package/dist/core/ThoughtProcessor.d.ts.map +1 -1
  69. package/dist/core/ThoughtProcessor.js +34 -38
  70. package/dist/core/ThoughtProcessor.js.map +1 -1
  71. package/dist/core/VerificationOutcomeAdmission.d.ts.map +1 -1
  72. package/dist/core/VerificationOutcomeAdmission.js +5 -7
  73. package/dist/core/VerificationOutcomeAdmission.js.map +1 -1
  74. package/dist/core/compression/CompressionService.d.ts +2 -1
  75. package/dist/core/compression/CompressionService.d.ts.map +1 -1
  76. package/dist/core/compression/CompressionService.js.map +1 -1
  77. package/dist/core/compression/DehydrationPolicy.d.ts.map +1 -1
  78. package/dist/core/compression/DehydrationPolicy.js.map +1 -1
  79. package/dist/core/compression/InMemorySummaryStore.d.ts +2 -1
  80. package/dist/core/compression/InMemorySummaryStore.d.ts.map +1 -1
  81. package/dist/core/compression/InMemorySummaryStore.js.map +1 -1
  82. package/dist/core/evaluator/Calibrator.d.ts +2 -2
  83. package/dist/core/evaluator/Calibrator.d.ts.map +1 -1
  84. package/dist/core/evaluator/Calibrator.js +2 -5
  85. package/dist/core/evaluator/Calibrator.js.map +1 -1
  86. package/dist/core/evaluator/SignalComputer.d.ts +2 -1
  87. package/dist/core/evaluator/SignalComputer.d.ts.map +1 -1
  88. package/dist/core/evaluator/SignalComputer.js.map +1 -1
  89. package/dist/core/evaluator/internals.d.ts +1 -5
  90. package/dist/core/evaluator/internals.d.ts.map +1 -1
  91. package/dist/core/evaluator/internals.js +1 -3
  92. package/dist/core/evaluator/internals.js.map +1 -1
  93. package/dist/core/graph/Edge.d.ts +1 -1
  94. package/dist/core/graph/EdgeEmitter.d.ts +2 -4
  95. package/dist/core/graph/EdgeEmitter.d.ts.map +1 -1
  96. package/dist/core/graph/EdgeEmitter.js +2 -4
  97. package/dist/core/graph/EdgeEmitter.js.map +1 -1
  98. package/dist/core/graph/GraphView.d.ts +13 -1
  99. package/dist/core/graph/GraphView.d.ts.map +1 -1
  100. package/dist/core/graph/GraphView.js +34 -0
  101. package/dist/core/graph/GraphView.js.map +1 -1
  102. package/dist/core/reasoning/OutcomeRecorder.d.ts +0 -1
  103. package/dist/core/reasoning/OutcomeRecorder.d.ts.map +1 -1
  104. package/dist/core/reasoning/OutcomeRecorder.js.map +1 -1
  105. package/dist/core/reasoning/strategies/SequentialStrategy.d.ts +3 -3
  106. package/dist/core/reasoning/strategies/SequentialStrategy.js.map +1 -1
  107. package/dist/core/reasoning/strategies/TreeOfThoughtStrategy.d.ts +6 -6
  108. package/dist/core/reasoning/strategies/TreeOfThoughtStrategy.d.ts.map +1 -1
  109. package/dist/core/reasoning/strategies/TreeOfThoughtStrategy.js +16 -2
  110. package/dist/core/reasoning/strategies/TreeOfThoughtStrategy.js.map +1 -1
  111. package/dist/core/reasoning/strategies/totScoring.d.ts.map +1 -1
  112. package/dist/core/reasoning/strategies/totScoring.js.map +1 -1
  113. package/dist/core/reasoning.d.ts +1 -3
  114. package/dist/core/reasoning.d.ts.map +1 -1
  115. package/dist/core/thought.d.ts +3 -2
  116. package/dist/core/thought.d.ts.map +1 -1
  117. package/dist/errors.d.ts +4 -20
  118. package/dist/errors.d.ts.map +1 -1
  119. package/dist/errors.js +2 -28
  120. package/dist/errors.js.map +1 -1
  121. package/dist/lib.d.ts +31 -24
  122. package/dist/lib.d.ts.map +1 -1
  123. package/dist/lib.js +55 -10
  124. package/dist/lib.js.map +1 -1
  125. package/dist/logger/NullLogger.d.ts +9 -11
  126. package/dist/logger/NullLogger.d.ts.map +1 -1
  127. package/dist/logger/NullLogger.js.map +1 -1
  128. package/dist/persistence/FilePersistence.d.ts +3 -12
  129. package/dist/persistence/FilePersistence.d.ts.map +1 -1
  130. package/dist/persistence/FilePersistence.js +9 -61
  131. package/dist/persistence/FilePersistence.js.map +1 -1
  132. package/dist/persistence/FileSnapshotV2.d.ts +2 -2
  133. package/dist/persistence/FileSnapshotV2.js +2 -2
  134. package/dist/persistence/FileSnapshotV2.js.map +1 -1
  135. package/dist/persistence/FileWriter.d.ts +1 -0
  136. package/dist/persistence/FileWriter.d.ts.map +1 -1
  137. package/dist/persistence/FileWriter.js +3 -3
  138. package/dist/persistence/FileWriter.js.map +1 -1
  139. package/dist/persistence/MemoryPersistence.d.ts +5 -15
  140. package/dist/persistence/MemoryPersistence.d.ts.map +1 -1
  141. package/dist/persistence/MemoryPersistence.js +6 -40
  142. package/dist/persistence/MemoryPersistence.js.map +1 -1
  143. package/dist/persistence/PersistenceCodec.js +1 -3
  144. package/dist/persistence/PersistenceCodec.js.map +1 -1
  145. package/dist/persistence/PersistenceErrors.d.ts +0 -6
  146. package/dist/persistence/PersistenceErrors.d.ts.map +1 -1
  147. package/dist/persistence/PersistenceErrors.js +1 -9
  148. package/dist/persistence/PersistenceErrors.js.map +1 -1
  149. package/dist/persistence/PersistenceScope.d.ts.map +1 -1
  150. package/dist/persistence/PersistenceScope.js +2 -2
  151. package/dist/persistence/PersistenceScope.js.map +1 -1
  152. package/dist/persistence/SqlitePersistence.d.ts +3 -10
  153. package/dist/persistence/SqlitePersistence.d.ts.map +1 -1
  154. package/dist/persistence/SqlitePersistence.js +2 -26
  155. package/dist/persistence/SqlitePersistence.js.map +1 -1
  156. package/dist/persistence/SqliteSchemaV2.d.ts.map +1 -1
  157. package/dist/persistence/SqliteSchemaV2.js +3 -12
  158. package/dist/persistence/SqliteSchemaV2.js.map +1 -1
  159. package/dist/pool/ConnectionPool.d.ts +3 -12
  160. package/dist/pool/ConnectionPool.d.ts.map +1 -1
  161. package/dist/pool/ConnectionPool.js +5 -8
  162. package/dist/pool/ConnectionPool.js.map +1 -1
  163. package/dist/pool/IConnectionPool.d.ts +3 -9
  164. package/dist/pool/IConnectionPool.d.ts.map +1 -1
  165. package/dist/registry/SkillRegistry.d.ts +1 -7
  166. package/dist/registry/SkillRegistry.d.ts.map +1 -1
  167. package/dist/registry/SkillRegistry.js +0 -15
  168. package/dist/registry/SkillRegistry.js.map +1 -1
  169. package/dist/registry/ToolRegistry.d.ts +1 -9
  170. package/dist/registry/ToolRegistry.d.ts.map +1 -1
  171. package/dist/registry/ToolRegistry.js +0 -21
  172. package/dist/registry/ToolRegistry.js.map +1 -1
  173. package/dist/schema.d.ts +2 -1
  174. package/dist/schema.d.ts.map +1 -1
  175. package/dist/schema.js +3 -3
  176. package/dist/schema.js.map +1 -1
  177. package/dist/transport/BaseTransport.d.ts +0 -9
  178. package/dist/transport/BaseTransport.d.ts.map +1 -1
  179. package/dist/transport/BaseTransport.js +0 -11
  180. package/dist/transport/BaseTransport.js.map +1 -1
  181. package/dist/transport/HttpHelpers.d.ts +4 -1
  182. package/dist/transport/HttpHelpers.d.ts.map +1 -1
  183. package/dist/transport/HttpHelpers.js +61 -11
  184. package/dist/transport/HttpHelpers.js.map +1 -1
  185. package/dist/transport/HttpRequestLifecycle.d.ts +15 -1
  186. package/dist/transport/HttpRequestLifecycle.d.ts.map +1 -1
  187. package/dist/transport/HttpRequestLifecycle.js +66 -4
  188. package/dist/transport/HttpRequestLifecycle.js.map +1 -1
  189. package/dist/transport/HttpTransport.d.ts +2 -0
  190. package/dist/transport/HttpTransport.d.ts.map +1 -1
  191. package/dist/transport/HttpTransport.js +75 -38
  192. package/dist/transport/HttpTransport.js.map +1 -1
  193. package/dist/transport/StreamableHttpTransport.d.ts +5 -29
  194. package/dist/transport/StreamableHttpTransport.d.ts.map +1 -1
  195. package/dist/transport/StreamableHttpTransport.js +74 -116
  196. package/dist/transport/StreamableHttpTransport.js.map +1 -1
  197. package/dist/watchers/SkillWatcher.d.ts.map +1 -1
  198. package/dist/watchers/SkillWatcher.js +1 -1
  199. package/dist/watchers/SkillWatcher.js.map +1 -1
  200. package/package.json +2 -2
  201. package/dist/contracts/PersistenceBackend.js.map +0 -1
  202. package/dist/persistence/FileLegacyImport.d.ts +0 -2
  203. package/dist/persistence/FileLegacyImport.d.ts.map +0 -1
  204. package/dist/persistence/FileLegacyImport.js +0 -183
  205. package/dist/persistence/FileLegacyImport.js.map +0 -1
  206. package/dist/persistence/SessionScopedPersistence.d.ts +0 -4
  207. package/dist/persistence/SessionScopedPersistence.d.ts.map +0 -1
  208. package/dist/persistence/SessionScopedPersistence.js +0 -9
  209. package/dist/persistence/SessionScopedPersistence.js.map +0 -1
  210. package/dist/persistence/SqliteLegacyImport.d.ts +0 -14
  211. package/dist/persistence/SqliteLegacyImport.d.ts.map +0 -1
  212. package/dist/persistence/SqliteLegacyImport.js +0 -136
  213. package/dist/persistence/SqliteLegacyImport.js.map +0 -1
  214. package/dist/persistence/SqliteLegacySchema.d.ts +0 -15
  215. package/dist/persistence/SqliteLegacySchema.d.ts.map +0 -1
  216. package/dist/persistence/SqliteLegacySchema.js +0 -72
  217. package/dist/persistence/SqliteLegacySchema.js.map +0 -1
@@ -7,7 +7,7 @@ import { BaseTransport } from "./BaseTransport.js";
7
7
  import { asSessionId } from "../contracts/ids.js";
8
8
  import { readRequestBody } from "./HttpHelpers.js";
9
9
  import { runWithContext } from "../context/RequestContext.js";
10
- import { AcceptedWorkTracker, LifecycleFailureReporter, ResponseFinalizer } from "./HttpRequestLifecycle.js";
10
+ import { AcceptedWorkTracker, LifecycleFailureReporter, PreDispatchTracker, ResponseFinalizer } from "./HttpRequestLifecycle.js";
11
11
  function parseSessionRetentionOptions(options) {
12
12
  const { maxSessions, sessionIdleTimeoutMs, sessionSweepIntervalMs } = options;
13
13
  if (void 0 === maxSessions && void 0 === sessionIdleTimeoutMs && void 0 === sessionSweepIntervalMs) return null;
@@ -37,6 +37,7 @@ class StreamableHttpTransport extends BaseTransport {
37
37
  _metricsProvider;
38
38
  _lifecycleFailureReporter;
39
39
  _acceptedWork;
40
+ _preDispatch = new PreDispatchTracker();
40
41
  _sessionRetention;
41
42
  _sessionSweepIntervalId = null;
42
43
  _stopPromise = null;
@@ -111,13 +112,16 @@ class StreamableHttpTransport extends BaseTransport {
111
112
  }
112
113
  });
113
114
  }
114
- async _handleRequest(req, res) {
115
+ _recordRequestMetrics(res) {
115
116
  const startTime = Date.now();
116
117
  this._metrics?.counter('streamable_http_requests_total', 1, {}, 'Total Streamable HTTP transport requests');
117
118
  res.once('finish', ()=>{
118
119
  const durationSeconds = (Date.now() - startTime) / 1000;
119
120
  this._metrics?.histogram('streamable_http_request_duration_seconds', durationSeconds, {});
120
121
  });
122
+ }
123
+ async _handleRequest(req, res) {
124
+ this._recordRequestMetrics(res);
121
125
  if (!this.validateHostHeader(req)) return void this._sendJsonRpcError(res, 403, -32000, 'Forbidden - invalid host header');
122
126
  if (this.isShuttingDown) return void this._sendJsonRpcError(res, 503, -32603, 'Server is shutting down');
123
127
  const clientIp = this.getClientIp(req);
@@ -139,11 +143,21 @@ class StreamableHttpTransport extends BaseTransport {
139
143
  if ('GET' === req.method && '/health' === urlPath) return void this._handleHealthCheck(res);
140
144
  if ('GET' === req.method && '/ready' === urlPath) return void await this._handleReadinessCheck(res);
141
145
  if (urlPath === this._path) {
142
- if ('POST' === req.method) return void this._acceptedWork.track(this._handleMcpPost(req, res));
143
- if ('GET' === req.method) return void this._handleMcpGet(req, res);
146
+ if ('POST' === req.method) {
147
+ const lease = this._preDispatch.acquire();
148
+ if (!lease) {
149
+ const responseFinalizer = new ResponseFinalizer(res, this._lifecycleFailureReporter);
150
+ responseFinalizer.finalize((response)=>{
151
+ this._sendJsonRpcError(response, 503, -32603, 'Server is shutting down');
152
+ });
153
+ return;
154
+ }
155
+ await this._handleMcpPost(req, res, lease);
156
+ return;
157
+ }
144
158
  res.writeHead(405, {
145
159
  'Content-Type': 'application/json',
146
- Allow: 'GET, POST'
160
+ Allow: 'POST'
147
161
  });
148
162
  res.end(JSON.stringify({
149
163
  jsonrpc: '2.0',
@@ -172,52 +186,82 @@ class StreamableHttpTransport extends BaseTransport {
172
186
  error
173
187
  }));
174
188
  }
175
- async _handleMcpPost(req, res) {
189
+ _parseJsonRpcRequest(body, responseFinalizer) {
190
+ let rawBody;
191
+ try {
192
+ rawBody = JSON.parse(body);
193
+ } catch {
194
+ responseFinalizer.finalize((response)=>{
195
+ this._sendJsonRpcError(response, 200, -32700, 'Parse error');
196
+ });
197
+ return null;
198
+ }
199
+ const parseResult = safeParse(JsonRpcRequestSchema, rawBody);
200
+ const rawId = rawBody && 'object' == typeof rawBody && 'id' in rawBody ? rawBody.id ?? null : null;
201
+ if (!parseResult.success) {
202
+ responseFinalizer.finalize((response)=>{
203
+ this._sendJsonRpcError(response, 200, -32600, 'Invalid Request', rawId, {
204
+ data: parseResult.issues
205
+ });
206
+ });
207
+ return null;
208
+ }
209
+ return parseResult.output;
210
+ }
211
+ async _handleMcpPost(req, res, lease) {
176
212
  this._requestCount++;
177
213
  this._activeRequests++;
178
214
  const responseFinalizer = new ResponseFinalizer(res, this._lifecycleFailureReporter);
179
215
  let acceptedSession = null;
216
+ const onAborted = ()=>lease.cancel('peer');
217
+ req.once('aborted', onAborted);
180
218
  const timeout = setTimeout(()=>{
181
219
  responseFinalizer.finalize((response)=>{
182
220
  this._sendJsonRpcError(response, 500, -32603, 'Request timeout');
183
221
  });
222
+ lease.cancel('timeout');
184
223
  }, this._requestTimeout);
185
224
  try {
186
- const body = await readRequestBody(req, this._bodySizeLimitEnabled ? this._maxBodySize : 0);
225
+ const body = await readRequestBody(req, this._bodySizeLimitEnabled ? this._maxBodySize : 0, lease.signal);
187
226
  if (null === body) return void responseFinalizer.finalize((response)=>{
188
227
  this._sendJsonRpcError(response, 413, -32000, 'Request body too large');
189
228
  });
190
- let rawBody;
191
- try {
192
- rawBody = JSON.parse(body);
193
- } catch {
229
+ const jsonRpcRequest = this._parseJsonRpcRequest(body, responseFinalizer);
230
+ if (null === jsonRpcRequest) return;
231
+ const session = this._acceptPostSession(req, responseFinalizer);
232
+ if (!session) return;
233
+ acceptedSession = session.state;
234
+ if (!this._mcpServer) {
235
+ const requestId = 'id' in jsonRpcRequest ? jsonRpcRequest.id ?? null : null;
194
236
  responseFinalizer.finalize((response)=>{
195
- this._sendJsonRpcError(response, 200, -32700, 'Parse error');
237
+ this._sendJsonRpcError(response, 503, -32603, 'Server not ready', requestId);
196
238
  });
197
239
  return;
198
240
  }
199
- const parseResult = safeParse(JsonRpcRequestSchema, rawBody);
200
- const rawId = rawBody && 'object' == typeof rawBody && 'id' in rawBody ? rawBody.id ?? null : null;
201
- if (!parseResult.success) return void responseFinalizer.finalize((response)=>{
202
- this._sendJsonRpcError(response, 200, -32600, 'Invalid Request', rawId, {
203
- data: parseResult.issues
204
- });
205
- });
206
- const jsonRpcRequest = parseResult.output;
207
- const session = this._acceptPostSession(req, responseFinalizer);
208
- if (!session) return;
209
- acceptedSession = session.state;
210
- if (!this._mcpServer) return void responseFinalizer.finalize((response)=>{
211
- this._sendJsonRpcError(response, 503, -32603, 'Server not ready', jsonRpcRequest?.id ?? null);
212
- });
213
- await this._receiveAndFinalize(jsonRpcRequest, session.id, responseFinalizer);
241
+ req.off('aborted', onAborted);
242
+ const acceptedWork = this._acceptedWork.transfer(lease, ()=>this._receiveAndFinalize(jsonRpcRequest, session.id, responseFinalizer).catch((error)=>{
243
+ responseFinalizer.finalize((response)=>{
244
+ this._sendJsonRpcError(response, 200, -32603, 'Internal error', null, {
245
+ data: getErrorMessage(error)
246
+ });
247
+ });
248
+ }));
249
+ await acceptedWork;
214
250
  } catch (error) {
251
+ if (lease.signal.aborted) {
252
+ if ('shutdown' === lease.cancellationReason) responseFinalizer.finalize((response)=>{
253
+ this._sendJsonRpcError(response, 503, -32603, 'Server is shutting down');
254
+ });
255
+ return;
256
+ }
215
257
  responseFinalizer.finalize((response)=>{
216
258
  this._sendJsonRpcError(response, 200, -32603, 'Internal error', null, {
217
259
  data: getErrorMessage(error)
218
260
  });
219
261
  });
220
262
  } finally{
263
+ req.off('aborted', onAborted);
264
+ lease.release();
221
265
  if (acceptedSession) acceptedSession.acceptedPostCount--;
222
266
  clearTimeout(timeout);
223
267
  this._activeRequests--;
@@ -270,70 +314,6 @@ class StreamableHttpTransport extends BaseTransport {
270
314
  httpResponse.end();
271
315
  });
272
316
  }
273
- _handleMcpGet(req, res) {
274
- if (!this._stateful) {
275
- res.writeHead(405, {
276
- 'Content-Type': 'application/json',
277
- Allow: 'POST'
278
- });
279
- res.end(JSON.stringify({
280
- jsonrpc: '2.0',
281
- id: null,
282
- error: {
283
- code: -32601,
284
- message: 'GET not supported in stateless mode'
285
- }
286
- }));
287
- return;
288
- }
289
- const sessionId = this._getSessionIdFromHeader(req);
290
- if (!sessionId) {
291
- res.writeHead(400, {
292
- 'Content-Type': 'application/json'
293
- });
294
- res.end(JSON.stringify({
295
- jsonrpc: '2.0',
296
- id: null,
297
- error: {
298
- code: -32600,
299
- message: 'Missing Mcp-Session-Id header'
300
- }
301
- }));
302
- return;
303
- }
304
- const session = this._sessions.get(asSessionId(sessionId));
305
- if (!session) {
306
- res.writeHead(404, {
307
- 'Content-Type': 'application/json'
308
- });
309
- res.end(JSON.stringify({
310
- jsonrpc: '2.0',
311
- id: null,
312
- error: {
313
- code: -32001,
314
- message: 'Session not found'
315
- }
316
- }));
317
- return;
318
- }
319
- session.lastActivityAt = Date.now();
320
- res.writeHead(200, {
321
- 'Content-Type': 'text/event-stream',
322
- 'Cache-Control': 'no-cache',
323
- Connection: 'keep-alive',
324
- 'Mcp-Session-Id': sessionId
325
- });
326
- this._sendSseEvent(res, 'connected', {
327
- sessionId,
328
- timestamp: Date.now()
329
- });
330
- session.notificationStreams.add(res);
331
- this._updateSessionMetrics();
332
- req.on('close', ()=>{
333
- session.notificationStreams.delete(res);
334
- this._updateSessionMetrics();
335
- });
336
- }
337
317
  _resolveSession(req) {
338
318
  const headerSessionId = this._getSessionIdFromHeader(req);
339
319
  if (headerSessionId) {
@@ -367,7 +347,6 @@ class StreamableHttpTransport extends BaseTransport {
367
347
  id: newSessionId,
368
348
  createdAt: now,
369
349
  lastActivityAt: now,
370
- notificationStreams: new Set(),
371
350
  acceptedPostCount: 0
372
351
  };
373
352
  this._sessions.set(newSessionId, sessionState);
@@ -384,15 +363,7 @@ class StreamableHttpTransport extends BaseTransport {
384
363
  for (const [sessionId, session] of this._sessions)if (0 === session.acceptedPostCount && now - session.lastActivityAt >= this._sessionRetention.sessionIdleTimeoutMs) this._disposeSession(sessionId);
385
364
  }
386
365
  _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();
366
+ if (!this._sessions.delete(sessionId)) return;
396
367
  this._updateSessionMetrics();
397
368
  }
398
369
  _stopSessionSweep() {
@@ -406,17 +377,6 @@ class StreamableHttpTransport extends BaseTransport {
406
377
  const value = req.headers['mcp-session-id'];
407
378
  if ('string' == typeof value && value.length > 0) return value;
408
379
  }
409
- _sendSseEvent(res, event, data) {
410
- try {
411
- res.write(`event: ${event}\n`);
412
- res.write(`data: ${JSON.stringify(data)}\n\n`);
413
- } catch {}
414
- }
415
- broadcastToSession(sessionId, event, data) {
416
- const session = this._sessions.get(asSessionId(sessionId));
417
- if (!session) return;
418
- for (const stream of session.notificationStreams)this._sendSseEvent(stream, event, data);
419
- }
420
380
  _handleMetrics(res) {
421
381
  if (!this._metricsProvider) {
422
382
  res.writeHead(404, {
@@ -467,13 +427,11 @@ class StreamableHttpTransport extends BaseTransport {
467
427
  }
468
428
  _updateSessionMetrics() {
469
429
  this._metrics?.gauge('streamable_http_active_sessions', this._sessions.size, {}, 'Active Streamable HTTP sessions');
470
- let totalStreams = 0;
471
- for (const session of this._sessions.values())totalStreams += session.notificationStreams.size;
472
- this._metrics?.gauge('streamable_http_notification_streams', totalStreams, {}, 'Active SSE notification streams');
473
430
  }
474
431
  stop(timeout) {
475
432
  if (this._stopPromise) return this._stopPromise;
476
433
  this._isShuttingDown = true;
434
+ this._preDispatch.closeAdmission('shutdown');
477
435
  this._stopRateLimitCleanup();
478
436
  this._stopSessionSweep();
479
437
  const shutdownTimeout = timeout ?? 30000;
@@ -501,7 +459,7 @@ class StreamableHttpTransport extends BaseTransport {
501
459
  });
502
460
  const stopPromise = Promise.all([
503
461
  serverClosed,
504
- this._acceptedWork.join()
462
+ this._preDispatch.join().then(()=>this._acceptedWork.join())
505
463
  ]).then(()=>{
506
464
  this.log('info', 'Streamable HTTP transport stopped');
507
465
  });
@@ -1 +1 @@
1
- {"version":3,"file":"transport/StreamableHttpTransport.js","sources":["../../src/transport/StreamableHttpTransport.ts"],"sourcesContent":["/**\n * Streamable HTTP Transport implementation (MCP spec recommended transport).\n *\n * This transport implements the MCP Streamable HTTP specification, which replaces\n * the deprecated SSE transport as the recommended HTTP-based transport since March 2025.\n *\n * Key features:\n * - POST /mcp for JSON-RPC requests (main MCP endpoint)\n * - GET /mcp for optional SSE server-to-client notifications\n * - Session management via Mcp-Session-Id header\n * - Supports both stateful (session-based) and stateless (per-request) modes\n * - Health endpoints (/health, /ready)\n *\n * @example\n * ```typescript\n * const transport = new StreamableHttpTransport({\n * port: 3000,\n * host: 'localhost',\n * stateful: true,\n * });\n * await transport.connect(server);\n * ```\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http';\nimport type { McpServer } from 'tmcp';\nimport { safeParse } from 'valibot';\nimport type { IMetrics } from '../contracts/interfaces.js';\nimport { getErrorMessage } from '../errors.js';\nimport { JsonRpcRequestSchema } from '../schema.js';\nimport { BaseTransport, type TransportOptions } from './BaseTransport.js';\nimport type { ITransport, TransportKind } from '../contracts/transport.js';\nimport { asSessionId, type SessionId } from '../contracts/ids.js';\nimport { readRequestBody } from './HttpHelpers.js';\nimport { runWithContext } from '../context/RequestContext.js';\nimport {\n\tAcceptedWorkTracker,\n\tLifecycleFailureReporter,\n\tResponseFinalizer,\n} from './HttpRequestLifecycle.js';\n\n/**\n * MCP Streamable HTTP transport options extending base TransportOptions.\n */\nexport interface StreamableHttpTransportOptions extends TransportOptions {\n\t/**\n\t * Path for the MCP endpoint\n\t * @default '/mcp'\n\t */\n\tpath?: string;\n\n\t/**\n\t * Metrics collector for transport telemetry\n\t */\n\tmetrics?: IMetrics;\n\n\t/**\n\t * Prometheus metrics provider function\n\t */\n\tmetricsProvider?: () => string;\n\n\t/**\n\t * Enable stateful session-based mode.\n\t * When true, sessions are tracked via Mcp-Session-Id header.\n\t * When false, each request is processed independently (stateless).\n\t * @default true\n\t */\n\tstateful?: boolean;\n\n\t/**\n\t * Custom session ID generator function.\n\t * @default () => randomUUID()\n\t */\n\tsessionIdGenerator?: () => string;\n\n\t/**\n\t * Enable request body size limit\n\t * @default true\n\t */\n\tenableBodySizeLimit?: boolean;\n\n\t/**\n\t * Maximum request body size in bytes\n\t * @default 10485760 (10MB)\n\t */\n\tmaxBodySize?: number;\n\n\t/**\n\t * Request timeout in milliseconds\n\t * @default 30000 (30 seconds)\n\t */\n\trequestTimeout?: number;\n\n\t/** Maximum retained sessions when idle expiry is enabled. */\n\tmaxSessions?: number;\n\n\t/** Idle duration before an unpinned session expires. */\n\tsessionIdleTimeoutMs?: number;\n\n\t/** Interval between idle-session sweeps. */\n\tsessionSweepIntervalMs?: number;\n}\n\ntype SessionRetentionOptions = {\n\treadonly maxSessions: number;\n\treadonly sessionIdleTimeoutMs: number;\n\treadonly sessionSweepIntervalMs: number;\n};\n\nfunction parseSessionRetentionOptions(\n\toptions: StreamableHttpTransportOptions\n): SessionRetentionOptions | null {\n\tconst { maxSessions, sessionIdleTimeoutMs, sessionSweepIntervalMs } = options;\n\tif (\n\t\tmaxSessions === undefined &&\n\t\tsessionIdleTimeoutMs === undefined &&\n\t\tsessionSweepIntervalMs === undefined\n\t) {\n\t\treturn null;\n\t}\n\tif (\n\t\tmaxSessions === undefined ||\n\t\t!Number.isFinite(maxSessions) ||\n\t\t!Number.isInteger(maxSessions) ||\n\t\tmaxSessions <= 0 ||\n\t\tsessionIdleTimeoutMs === undefined ||\n\t\t!Number.isFinite(sessionIdleTimeoutMs) ||\n\t\t!Number.isInteger(sessionIdleTimeoutMs) ||\n\t\tsessionIdleTimeoutMs <= 0 ||\n\t\tsessionSweepIntervalMs === undefined ||\n\t\t!Number.isFinite(sessionSweepIntervalMs) ||\n\t\t!Number.isInteger(sessionSweepIntervalMs) ||\n\t\tsessionSweepIntervalMs <= 0\n\t) {\n\t\tthrow new TypeError(\n\t\t\t'maxSessions, sessionIdleTimeoutMs, and sessionSweepIntervalMs must be provided together as positive integers'\n\t\t);\n\t}\n\treturn {\n\t\tmaxSessions,\n\t\tsessionIdleTimeoutMs,\n\t\tsessionSweepIntervalMs,\n\t};\n}\n\n/**\n * Internal session state for stateful mode.\n */\ninterface SessionState {\n\t/** Unique session identifier */\n\tid: SessionId;\n\t/** Timestamp when the session was created */\n\tcreatedAt: number;\n\t/** Timestamp of the last activity */\n\tlastActivityAt: number;\n\t/** Active SSE notification streams for this session */\n\tnotificationStreams: Set<ServerResponse>;\n\tacceptedPostCount: number;\n}\n\ntype SessionError = {\n\treadonly statusCode: number;\n\treadonly code: number;\n\treadonly message: string;\n};\n\ntype AcceptedPostSession = {\n\treadonly id: string | undefined;\n\treadonly state: SessionState | null;\n};\n\n/**\n * Streamable HTTP Transport for MCP server.\n *\n * This transport implements the MCP Streamable HTTP specification,\n * providing JSON-RPC over HTTP with optional session management\n * and server-to-client SSE notification streams.\n *\n * @remarks\n * **Security Features (inherited from BaseTransport):**\n * - Session ID validation (alphanumeric, max 64 chars)\n * - Query parameter sanitization (whitelist allowed keys)\n * - Rate limiting per IP (configurable, default 100 req/min)\n * - CORS origin validation\n * - Host header validation\n *\n * **MCP Streamable HTTP Spec Compliance:**\n * - POST /mcp — JSON-RPC method calls\n * - GET /mcp — SSE notification stream (server-to-client)\n * - Mcp-Session-Id header for session management\n * - Content-Type: application/json for JSON-RPC responses\n * - Content-Type: text/event-stream for SSE notification streams\n *\n * **HTTP Status Code Mapping:**\n * - 200: Success (JSON-RPC response or SSE stream)\n * - 202: Accepted (JSON-RPC notification, no response body)\n * - 204: CORS Preflight (empty body)\n * - 400: Bad Request (invalid JSON, invalid session ID)\n * - 403: Forbidden (invalid CORS, invalid host)\n * - 404: Not Found\n * - 405: Method Not Allowed\n * - 413: Payload Too Large\n * - 429: Too Many Requests\n * - 500: Internal Server Error\n * - 503: Server Not Ready / Shutting Down\n */\nexport class StreamableHttpTransport extends BaseTransport implements ITransport {\n\tget kind(): TransportKind {\n\t\treturn 'streamable-http';\n\t}\n\tprivate _server: Server | null = null;\n\tprivate _mcpServer: McpServer | null = null;\n\tprivate _path: string;\n\tprivate _stateful: boolean;\n\tprivate _sessionIdGenerator: () => string;\n\tprivate _sessions: Map<SessionId, SessionState> = new Map();\n\tprivate _requestCount: number = 0;\n\tprivate _activeRequests: number = 0;\n\tprivate _bodySizeLimitEnabled: boolean;\n\tprivate _maxBodySize: number;\n\tprivate _requestTimeout: number;\n\tprivate _metrics?: IMetrics;\n\tprivate _metricsProvider: (() => string) | null;\n\tprivate readonly _lifecycleFailureReporter: LifecycleFailureReporter;\n\tprivate readonly _acceptedWork: AcceptedWorkTracker;\n\tprivate readonly _sessionRetention: SessionRetentionOptions | null;\n\tprivate _sessionSweepIntervalId: NodeJS.Timeout | null = null;\n\tprivate _stopPromise: Promise<void> | null = null;\n\n\tconstructor(options: StreamableHttpTransportOptions = {}) {\n\t\tconst sessionRetention = parseSessionRetentionOptions(options);\n\t\tsuper(options);\n\n\t\tthis._path = options.path ?? '/mcp';\n\t\tthis._stateful = options.stateful ?? true;\n\t\tthis._sessionIdGenerator = options.sessionIdGenerator ?? (() => randomUUID());\n\t\tthis._bodySizeLimitEnabled = options.enableBodySizeLimit ?? true;\n\t\tthis._maxBodySize = options.maxBodySize ?? 10 * 1024 * 1024;\n\t\tthis._requestTimeout = options.requestTimeout ?? 30000;\n\t\tthis._metrics = options.metrics;\n\t\tthis._metricsProvider = options.metricsProvider ?? null;\n\t\tthis._sessionRetention = sessionRetention;\n\t\tthis._lifecycleFailureReporter = new LifecycleFailureReporter((error) => {\n\t\t\tthis.log('error', 'Streamable HTTP request lifecycle failed', {\n\t\t\t\terror: getErrorMessage(error),\n\t\t\t});\n\t\t});\n\t\tthis._acceptedWork = new AcceptedWorkTracker(this._lifecycleFailureReporter);\n\t\tif (this._stateful && sessionRetention) {\n\t\t\tthis._sessionSweepIntervalId = setInterval(\n\t\t\t\t() => this._sweepExpiredSessions(),\n\t\t\t\tsessionRetention.sessionSweepIntervalMs\n\t\t\t);\n\t\t\tthis._sessionSweepIntervalId.unref();\n\t\t}\n\t}\n\n\tprivate _requireMcpServer(): McpServer {\n\t\tif (!this._mcpServer) {\n\t\t\tthrow new Error('MCP server not initialized. Did you call connect()?');\n\t\t}\n\t\treturn this._mcpServer;\n\t}\n\n\t/**\n\t * Get number of active sessions (stateful) or active requests (stateless).\n\t */\n\tget clientCount(): number {\n\t\treturn this._stateful ? this._sessions.size : this._activeRequests;\n\t}\n\n\t/**\n\t * Get the total number of requests handled.\n\t */\n\tget requestCount(): number {\n\t\treturn this._requestCount;\n\t}\n\n\t/**\n\t * Connects MCP server to this transport and starts listening.\n\t */\n\tasync connect(mcpServer: McpServer): Promise<void> {\n\t\tthis._mcpServer = mcpServer;\n\t\tconst server = createServer((req, res) => {\n\t\t\tconst responseFinalizer = new ResponseFinalizer(res, this._lifecycleFailureReporter);\n\t\t\tvoid this._handleRequest(req, res).catch((error: unknown) => {\n\t\t\t\tthis._lifecycleFailureReporter.report(error);\n\t\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\t\tthis._sendJsonRpcError(response, 500, -32603, 'Internal error', null, {\n\t\t\t\t\t\tdata: getErrorMessage(error),\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t});\n\t\t});\n\t\tthis._server = server;\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst cleanup = (): void => {\n\t\t\t\tserver.off('error', onError);\n\t\t\t\tserver.off('listening', onListening);\n\t\t\t};\n\t\t\tconst onError = (error: Error): void => {\n\t\t\t\tcleanup();\n\t\t\t\treject(error);\n\t\t\t};\n\t\t\tconst onListening = (): void => {\n\t\t\t\tcleanup();\n\t\t\t\tthis.log(\n\t\t\t\t\t'info',\n\t\t\t\t\t`Streamable HTTP transport listening on http://${this._host}:${this._port}`\n\t\t\t\t);\n\t\t\t\tresolve();\n\t\t\t};\n\t\t\tserver.once('error', onError);\n\t\t\tserver.once('listening', onListening);\n\t\t\ttry {\n\t\t\t\tserver.listen(this._port, this._host);\n\t\t\t} catch (error) {\n\t\t\t\tcleanup();\n\t\t\t\treject(error);\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Route and handle incoming HTTP requests.\n\t */\n\tprivate async _handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {\n\t\tconst startTime = Date.now();\n\t\tthis._metrics?.counter(\n\t\t\t'streamable_http_requests_total',\n\t\t\t1,\n\t\t\t{},\n\t\t\t'Total Streamable HTTP transport requests'\n\t\t);\n\t\tres.once('finish', () => {\n\t\t\tconst durationSeconds = (Date.now() - startTime) / 1000;\n\t\t\tthis._metrics?.histogram('streamable_http_request_duration_seconds', durationSeconds, {});\n\t\t});\n\n\t\t// Host validation\n\t\tif (!this.validateHostHeader(req)) {\n\t\t\tthis._sendJsonRpcError(res, 403, -32000, 'Forbidden - invalid host header');\n\t\t\treturn;\n\t\t}\n\n\t\t// Shutdown check\n\t\tif (this.isShuttingDown) {\n\t\t\tthis._sendJsonRpcError(res, 503, -32603, 'Server is shutting down');\n\t\t\treturn;\n\t\t}\n\n\t\t// Rate limiting\n\t\tconst clientIp = this.getClientIp(req);\n\t\tif (this.checkRateLimit(clientIp)) {\n\t\t\tres.setHeader('Retry-After', '60');\n\t\t\tthis._sendJsonRpcError(res, 429, -32000, 'Too many requests');\n\t\t\treturn;\n\t\t}\n\n\t\t// CORS validation\n\t\tif (!this.validateCorsOrigin(req)) {\n\t\t\tthis._sendJsonRpcError(res, 403, -32000, 'Forbidden - invalid origin');\n\t\t\treturn;\n\t\t}\n\n\t\tthis.setCorsHeaders(res);\n\n\t\t// CORS preflight\n\t\tif (req.method === 'OPTIONS') {\n\t\t\tres.setHeader('Access-Control-Allow-Headers', 'Content-Type, Mcp-Session-Id');\n\t\t\tres.writeHead(204);\n\t\t\tres.end();\n\t\t\treturn;\n\t\t}\n\n\t\t// Parse URL path\n\t\tconst urlPath = req.url?.split('?')[0] ?? '/';\n\n\t\t// Metrics endpoint\n\t\tif (req.method === 'GET' && urlPath === '/metrics') {\n\t\t\tthis._handleMetrics(res);\n\t\t\treturn;\n\t\t}\n\n\t\t// Health check (liveness)\n\t\tif (req.method === 'GET' && urlPath === '/health') {\n\t\t\tthis._handleHealthCheck(res);\n\t\t\treturn;\n\t\t}\n\n\t\t// Readiness check\n\t\tif (req.method === 'GET' && urlPath === '/ready') {\n\t\t\tawait this._handleReadinessCheck(res);\n\t\t\treturn;\n\t\t}\n\n\t\t// MCP endpoint routing\n\t\tif (urlPath === this._path) {\n\t\t\tif (req.method === 'POST') {\n\t\t\t\tthis._acceptedWork.track(this._handleMcpPost(req, res));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (req.method === 'GET') {\n\t\t\t\tthis._handleMcpGet(req, res);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tres.writeHead(405, { 'Content-Type': 'application/json', Allow: 'GET, POST' });\n\t\t\tres.end(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tjsonrpc: '2.0',\n\t\t\t\t\tid: null,\n\t\t\t\t\terror: { code: -32601, message: 'Method not allowed' },\n\t\t\t\t})\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\t// 404 for unknown paths\n\t\tthis._sendJsonRpcError(res, 404, -32601, 'Not Found');\n\t}\n\n\t/**\n\t * Send a JSON-RPC error response.\n\t */\n\tprivate _sendJsonRpcError(\n\t\tres: ServerResponse,\n\t\tstatusCode: number,\n\t\tcode: number,\n\t\tmessage: string,\n\t\tid: unknown = null,\n\t\textra?: Record<string, unknown>\n\t): void {\n\t\tconst error: Record<string, unknown> = { code, message };\n\t\tif (extra) {\n\t\t\tObject.assign(error, extra);\n\t\t}\n\t\tres.writeHead(statusCode, { 'Content-Type': 'application/json' });\n\t\tres.end(JSON.stringify({ jsonrpc: '2.0', id, error }));\n\t}\n\n\t/**\n\t * Handle POST /mcp — JSON-RPC method calls.\n\t *\n\t * Per the MCP Streamable HTTP spec:\n\t * - Accepts JSON-RPC request bodies\n\t * - Returns Mcp-Session-Id header for new sessions (stateful mode)\n\t * - Validates Mcp-Session-Id for existing sessions (stateful mode)\n\t * - Content-Type: application/json for responses\n\t */\n\tprivate async _handleMcpPost(req: IncomingMessage, res: ServerResponse): Promise<void> {\n\t\tthis._requestCount++;\n\t\tthis._activeRequests++;\n\t\tconst responseFinalizer = new ResponseFinalizer(res, this._lifecycleFailureReporter);\n\t\tlet acceptedSession: SessionState | null = null;\n\n\t\tconst timeout = setTimeout(() => {\n\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\tthis._sendJsonRpcError(response, 500, -32603, 'Request timeout');\n\t\t\t});\n\t\t}, this._requestTimeout);\n\t\ttry {\n\t\t\t// Read request body with size limit\n\t\t\tconst body = await readRequestBody(req, this._bodySizeLimitEnabled ? this._maxBodySize : 0);\n\t\t\tif (body === null) {\n\t\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\t\tthis._sendJsonRpcError(response, 413, -32000, 'Request body too large');\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Parse JSON\n\t\t\tlet rawBody: unknown;\n\t\t\ttry {\n\t\t\t\trawBody = JSON.parse(body) as unknown;\n\t\t\t} catch {\n\t\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\t\tthis._sendJsonRpcError(response, 200, -32700, 'Parse error');\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Validate JSON-RPC schema\n\t\t\tconst parseResult = safeParse(JsonRpcRequestSchema, rawBody);\n\t\t\tconst rawId =\n\t\t\t\trawBody && typeof rawBody === 'object' && 'id' in rawBody\n\t\t\t\t\t? ((rawBody as { id?: unknown }).id ?? null)\n\t\t\t\t\t: null;\n\t\t\tif (!parseResult.success) {\n\t\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\t\tthis._sendJsonRpcError(\n\t\t\t\t\t\tresponse,\n\t\t\t\t\t\t200,\n\t\t\t\t\t\t-32600,\n\t\t\t\t\t\t'Invalid Request',\n\t\t\t\t\t\trawId as string | number | null,\n\t\t\t\t\t\t{ data: parseResult.issues }\n\t\t\t\t\t);\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst jsonRpcRequest = parseResult.output;\n\n\t\t\tconst session = this._acceptPostSession(req, responseFinalizer);\n\t\t\tif (!session) return;\n\t\t\tacceptedSession = session.state;\n\n\t\t\t// Check if MCP server is ready\n\t\t\tif (!this._mcpServer) {\n\t\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\t\tthis._sendJsonRpcError(\n\t\t\t\t\t\tresponse,\n\t\t\t\t\t\t503,\n\t\t\t\t\t\t-32603,\n\t\t\t\t\t\t'Server not ready',\n\t\t\t\t\t\tjsonRpcRequest?.id ?? null\n\t\t\t\t\t);\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tawait this._receiveAndFinalize(\n\t\t\t\tjsonRpcRequest as Parameters<McpServer['receive']>[0],\n\t\t\t\tsession.id,\n\t\t\t\tresponseFinalizer\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\tthis._sendJsonRpcError(response, 200, -32603, 'Internal error', null, {\n\t\t\t\t\tdata: getErrorMessage(error),\n\t\t\t\t});\n\t\t\t});\n\t\t} finally {\n\t\t\tif (acceptedSession) acceptedSession.acceptedPostCount--;\n\t\t\tclearTimeout(timeout);\n\t\t\tthis._activeRequests--;\n\t\t}\n\t}\n\n\tprivate _acceptPostSession(\n\t\treq: IncomingMessage,\n\t\tresponseFinalizer: ResponseFinalizer\n\t): AcceptedPostSession | null {\n\t\tif (!this._stateful) return { id: undefined, state: null };\n\t\tif (this.isShuttingDown) {\n\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\tthis._sendJsonRpcError(response, 503, -32603, 'Server is shutting down');\n\t\t\t});\n\t\t\treturn null;\n\t\t}\n\t\tconst session = this._resolveSession(req);\n\t\tif (!('state' in session)) {\n\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\tthis._sendJsonRpcError(response, session.statusCode, session.code, session.message);\n\t\t\t});\n\t\t\treturn null;\n\t\t}\n\t\tsession.state.acceptedPostCount++;\n\t\treturn { id: session.id, state: session.state };\n\t}\n\n\tprivate async _receiveAndFinalize(\n\t\tjsonRpcRequest: Parameters<McpServer['receive']>[0],\n\t\tsessionId: string | undefined,\n\t\tresponseFinalizer: ResponseFinalizer\n\t): Promise<void> {\n\t\tconst owner = this._stateful ? (sessionId ?? randomUUID()) : randomUUID();\n\t\tconst response = await runWithContext({ requestId: randomUUID(), owner }, () =>\n\t\t\tthis._requireMcpServer().receive(jsonRpcRequest, { sessionInfo: {} })\n\t\t);\n\t\tconst responseHeaders: Record<string, string> = { 'Content-Type': 'application/json' };\n\t\tif (sessionId) responseHeaders['Mcp-Session-Id'] = sessionId;\n\n\t\tresponseFinalizer.finalize((httpResponse) => {\n\t\t\tif (response) {\n\t\t\t\thttpResponse.writeHead(200, responseHeaders);\n\t\t\t\thttpResponse.end(JSON.stringify(response));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (sessionId) httpResponse.setHeader('Mcp-Session-Id', sessionId);\n\t\t\thttpResponse.writeHead(202);\n\t\t\thttpResponse.end();\n\t\t});\n\t}\n\n\t/**\n\t * Handle GET /mcp — Optional SSE notification stream.\n\t *\n\t * Per the MCP Streamable HTTP spec, clients may open a GET request\n\t * to receive server-initiated notifications as SSE events.\n\t * Requires a valid Mcp-Session-Id in stateful mode.\n\t */\n\tprivate _handleMcpGet(req: IncomingMessage, res: ServerResponse): void {\n\t\tif (!this._stateful) {\n\t\t\t// SSE notification streams require stateful mode\n\t\t\tres.writeHead(405, {\n\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\tAllow: 'POST',\n\t\t\t});\n\t\t\tres.end(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tjsonrpc: '2.0',\n\t\t\t\t\tid: null,\n\t\t\t\t\terror: { code: -32601, message: 'GET not supported in stateless mode' },\n\t\t\t\t})\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\t// Require Mcp-Session-Id for GET requests\n\t\tconst sessionId = this._getSessionIdFromHeader(req);\n\t\tif (!sessionId) {\n\t\t\tres.writeHead(400, { 'Content-Type': 'application/json' });\n\t\t\tres.end(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tjsonrpc: '2.0',\n\t\t\t\t\tid: null,\n\t\t\t\t\terror: { code: -32600, message: 'Missing Mcp-Session-Id header' },\n\t\t\t\t})\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tconst session = this._sessions.get(asSessionId(sessionId));\n\t\tif (!session) {\n\t\t\tres.writeHead(404, { 'Content-Type': 'application/json' });\n\t\t\tres.end(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tjsonrpc: '2.0',\n\t\t\t\t\tid: null,\n\t\t\t\t\terror: { code: -32001, message: 'Session not found' },\n\t\t\t\t})\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tsession.lastActivityAt = Date.now();\n\n\t\t// Set SSE headers\n\t\tres.writeHead(200, {\n\t\t\t'Content-Type': 'text/event-stream',\n\t\t\t'Cache-Control': 'no-cache',\n\t\t\tConnection: 'keep-alive',\n\t\t\t'Mcp-Session-Id': sessionId,\n\t\t});\n\n\t\t// Send initial connected event\n\t\tthis._sendSseEvent(res, 'connected', {\n\t\t\tsessionId,\n\t\t\ttimestamp: Date.now(),\n\t\t});\n\n\t\t// Track this notification stream\n\t\tsession.notificationStreams.add(res);\n\t\tthis._updateSessionMetrics();\n\n\t\t// Handle client disconnect\n\t\treq.on('close', () => {\n\t\t\tsession.notificationStreams.delete(res);\n\t\t\tthis._updateSessionMetrics();\n\t\t});\n\t}\n\n\t/**\n\t * Resolve or create a session for a stateful request.\n\t *\n\t * @returns Session ID string on success, or the response error to finalize.\n\t */\n\tprivate _resolveSession(\n\t\treq: IncomingMessage\n\t): { readonly id: SessionId; readonly state: SessionState } | SessionError {\n\t\tconst headerSessionId = this._getSessionIdFromHeader(req);\n\n\t\tif (headerSessionId) {\n\t\t\t// Validate format\n\t\t\tif (!this.validateSessionId(headerSessionId)) {\n\t\t\t\treturn {\n\t\t\t\t\tstatusCode: 400,\n\t\t\t\t\tcode: -32600,\n\t\t\t\t\tmessage: 'Invalid Mcp-Session-Id format',\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// Check if session exists\n\t\t\tconst session = this._sessions.get(asSessionId(headerSessionId));\n\t\t\tif (session) {\n\t\t\t\tsession.lastActivityAt = Date.now();\n\t\t\t\treturn { id: session.id, state: session };\n\t\t\t}\n\n\t\t\t// Unknown session ID — per spec, return 404\n\t\t\treturn { statusCode: 404, code: -32001, message: 'Session not found' };\n\t\t}\n\n\t\t// No session header — create new session\n\t\tif (this._sessionRetention && this._sessions.size >= this._sessionRetention.maxSessions) {\n\t\t\treturn { statusCode: 503, code: -32000, message: 'Session capacity reached' };\n\t\t}\n\t\tconst newSessionId = asSessionId(this._sessionIdGenerator());\n\t\tconst now = Date.now();\n\t\tconst sessionState: SessionState = {\n\t\t\tid: newSessionId,\n\t\t\tcreatedAt: now,\n\t\t\tlastActivityAt: now,\n\t\t\tnotificationStreams: new Set(),\n\t\t\tacceptedPostCount: 0,\n\t\t};\n\t\tthis._sessions.set(newSessionId, sessionState);\n\t\tthis.log('info', `New session created: ${newSessionId}`);\n\t\tthis._updateSessionMetrics();\n\n\t\treturn { id: newSessionId, state: sessionState };\n\t}\n\n\tprivate _sweepExpiredSessions(): void {\n\t\tif (!this._sessionRetention) return;\n\t\tconst now = Date.now();\n\t\tfor (const [sessionId, session] of this._sessions) {\n\t\t\tif (\n\t\t\t\tsession.acceptedPostCount === 0 &&\n\t\t\t\tnow - session.lastActivityAt >= this._sessionRetention.sessionIdleTimeoutMs\n\t\t\t) {\n\t\t\t\tthis._disposeSession(sessionId);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate _disposeSession(sessionId: SessionId): void {\n\t\tconst session = this._sessions.get(sessionId);\n\t\tif (!session) return;\n\t\tthis._sessions.delete(sessionId);\n\t\tfor (const stream of session.notificationStreams) {\n\t\t\ttry {\n\t\t\t\tstream.end();\n\t\t\t} catch (error) {\n\t\t\t\tthis._lifecycleFailureReporter.report(error);\n\t\t\t}\n\t\t}\n\t\tsession.notificationStreams.clear();\n\t\tthis._updateSessionMetrics();\n\t}\n\n\tprivate _stopSessionSweep(): void {\n\t\tconst interval = this._sessionSweepIntervalId;\n\t\tif (!interval) return;\n\t\tinterval.unref();\n\t\tclearInterval(interval);\n\t\tthis._sessionSweepIntervalId = null;\n\t}\n\n\t/**\n\t * Extract Mcp-Session-Id from request headers.\n\t */\n\tprivate _getSessionIdFromHeader(req: IncomingMessage): string | undefined {\n\t\tconst value = req.headers['mcp-session-id'];\n\t\tif (typeof value === 'string' && value.length > 0) {\n\t\t\treturn value;\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Send an SSE event to a specific client response stream.\n\t */\n\tprivate _sendSseEvent(res: ServerResponse, event: string, data: unknown): void {\n\t\ttry {\n\t\t\tres.write(`event: ${event}\\n`);\n\t\t\tres.write(`data: ${JSON.stringify(data)}\\n\\n`);\n\t\t} catch {\n\t\t\t// Client disconnected — ignore\n\t\t}\n\t}\n\n\t/**\n\t * Broadcast a notification to all SSE streams in a given session.\n\t *\n\t * @param sessionId - Target session ID\n\t * @param event - SSE event name\n\t * @param data - Event payload\n\t */\n\tbroadcastToSession(sessionId: string, event: string, data: unknown): void {\n\t\tconst session = this._sessions.get(asSessionId(sessionId));\n\t\tif (!session) {\n\t\t\treturn;\n\t\t}\n\n\t\tfor (const stream of session.notificationStreams) {\n\t\t\tthis._sendSseEvent(stream, event, data);\n\t\t}\n\t}\n\n\t/**\n\t * Handle GET /metrics endpoint.\n\t */\n\tprivate _handleMetrics(res: ServerResponse): void {\n\t\tif (!this._metricsProvider) {\n\t\t\tres.writeHead(404, { 'Content-Type': 'text/plain' });\n\t\t\tres.end('Not Found');\n\t\t\treturn;\n\t\t}\n\n\t\tres.writeHead(200, { 'Content-Type': 'text/plain; version=0.0.4; charset=utf-8' });\n\t\tres.end(this._metricsProvider());\n\t}\n\n\t/**\n\t * Handle GET /health — Liveness check.\n\t */\n\tprivate _handleHealthCheck(res: ServerResponse): void {\n\t\tconst healthData: Record<string, unknown> = {\n\t\t\tstatus: 'healthy',\n\t\t\trequests: this._requestCount,\n\t\t\tsessions: this._sessions.size,\n\t\t\ttransport: 'streamable-http',\n\t\t};\n\t\tif (this._healthChecker) {\n\t\t\tconst liveness = this._healthChecker.checkLiveness();\n\t\t\thealthData.liveness = liveness;\n\t\t}\n\t\tres.writeHead(200, { 'Content-Type': 'application/json' });\n\t\tres.end(JSON.stringify(healthData));\n\t}\n\n\t/**\n\t * Handle GET /ready — Readiness check.\n\t */\n\tprivate async _handleReadinessCheck(res: ServerResponse): Promise<void> {\n\t\tif (this._healthChecker) {\n\t\t\tconst readiness = await this._healthChecker.checkReadiness();\n\t\t\tconst statusCode = readiness.status === 'ok' ? 200 : 503;\n\t\t\tres.writeHead(statusCode, { 'Content-Type': 'application/json' });\n\t\t\tres.end(JSON.stringify(readiness));\n\t\t} else {\n\t\t\tres.writeHead(200, { 'Content-Type': 'application/json' });\n\t\t\tres.end(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tstatus: 'ok',\n\t\t\t\t\ttimestamp: new Date().toISOString(),\n\t\t\t\t\tcomponents: {},\n\t\t\t\t})\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Update session-related metrics.\n\t */\n\tprivate _updateSessionMetrics(): void {\n\t\tthis._metrics?.gauge(\n\t\t\t'streamable_http_active_sessions',\n\t\t\tthis._sessions.size,\n\t\t\t{},\n\t\t\t'Active Streamable HTTP sessions'\n\t\t);\n\n\t\tlet totalStreams = 0;\n\t\tfor (const session of this._sessions.values()) {\n\t\t\ttotalStreams += session.notificationStreams.size;\n\t\t}\n\t\tthis._metrics?.gauge(\n\t\t\t'streamable_http_notification_streams',\n\t\t\ttotalStreams,\n\t\t\t{},\n\t\t\t'Active SSE notification streams'\n\t\t);\n\t}\n\n\t/**\n\t * Stop transport server with graceful shutdown.\n\t *\n\t * @param timeout - Maximum time before force-closing HTTP connections (default: 30s)\n\t */\n\tstop(timeout?: number): Promise<void> {\n\t\tif (this._stopPromise) return this._stopPromise;\n\t\tthis._isShuttingDown = true;\n\t\tthis._stopRateLimitCleanup();\n\t\tthis._stopSessionSweep();\n\n\t\tconst shutdownTimeout = timeout ?? 30000;\n\n\t\tfor (const sessionId of this._sessions.keys()) {\n\t\t\tthis._disposeSession(sessionId);\n\t\t}\n\n\t\tconst server = this._server;\n\t\tconst serverClosed = new Promise<void>((resolve, reject) => {\n\t\t\tif (!server?.listening) {\n\t\t\t\tresolve();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet settled = false;\n\t\t\tlet forceClose: NodeJS.Timeout | null = null;\n\t\t\tserver.close((error) => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tif (forceClose) clearTimeout(forceClose);\n\t\t\t\tif (error) {\n\t\t\t\t\treject(error);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tresolve();\n\t\t\t});\n\t\t\tif (settled) return;\n\t\t\tforceClose = setTimeout(() => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tthis.log('warn', 'Streamable HTTP transport force-closing after timeout');\n\t\t\t\tserver.closeAllConnections();\n\t\t\t\tresolve();\n\t\t\t}, shutdownTimeout);\n\t\t});\n\t\tconst stopPromise = Promise.all([serverClosed, this._acceptedWork.join()]).then(() => {\n\t\t\tthis.log('info', 'Streamable HTTP transport stopped');\n\t\t});\n\t\tthis._stopPromise = stopPromise;\n\t\tvoid stopPromise.catch(() => {\n\t\t\tif (this._stopPromise === stopPromise) this._stopPromise = null;\n\t\t});\n\t\treturn stopPromise;\n\t}\n}\n\n/**\n * Create a Streamable HTTP transport with given options.\n *\n * @param options - Transport configuration\n * @returns A configured Streamable HTTP transport\n *\n * @example\n * ```typescript\n * const transport = createStreamableHttpTransport({ port: 3000, stateful: true });\n * await transport.connect(server);\n * ```\n */\nexport function createStreamableHttpTransport(\n\toptions: StreamableHttpTransportOptions = {}\n): StreamableHttpTransport {\n\treturn new StreamableHttpTransport(options);\n}\n"],"names":["parseSessionRetentionOptions","options","maxSessions","sessionIdleTimeoutMs","sessionSweepIntervalMs","undefined","Number","TypeError","StreamableHttpTransport","BaseTransport","Map","sessionRetention","randomUUID","LifecycleFailureReporter","error","getErrorMessage","AcceptedWorkTracker","setInterval","Error","mcpServer","server","createServer","req","res","responseFinalizer","ResponseFinalizer","response","Promise","resolve","reject","cleanup","onError","onListening","startTime","Date","durationSeconds","clientIp","urlPath","JSON","statusCode","code","message","id","extra","Object","acceptedSession","timeout","setTimeout","body","readRequestBody","rawBody","parseResult","safeParse","JsonRpcRequestSchema","rawId","jsonRpcRequest","session","clearTimeout","sessionId","owner","runWithContext","responseHeaders","httpResponse","asSessionId","headerSessionId","newSessionId","now","sessionState","Set","stream","interval","clearInterval","value","event","data","healthData","liveness","readiness","totalStreams","shutdownTimeout","serverClosed","settled","forceClose","stopPromise","createStreamableHttpTransport"],"mappings":"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"}
1
+ {"version":3,"file":"transport/StreamableHttpTransport.js","sources":["../../src/transport/StreamableHttpTransport.ts"],"sourcesContent":["/**\n * Streamable HTTP Transport implementation (MCP spec recommended transport).\n *\n * Key features:\n * - POST /mcp for JSON-RPC requests (main MCP endpoint)\n * - Session management via Mcp-Session-Id header\n * - Supports both stateful (session-based) and stateless (per-request) modes\n * - Health endpoints (/health, /ready)\n *\n * @example\n * ```typescript\n * const transport = new StreamableHttpTransport({\n * port: 3000,\n * host: 'localhost',\n * stateful: true,\n * });\n * await transport.connect(server);\n * ```\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http';\nimport type { McpServer } from 'tmcp';\nimport { safeParse } from 'valibot';\nimport type { IMetrics } from '../contracts/interfaces.js';\nimport { getErrorMessage } from '../errors.js';\nimport { JsonRpcRequestSchema } from '../schema.js';\nimport { BaseTransport, type TransportOptions } from './BaseTransport.js';\nimport type { ITransport, TransportKind } from '../contracts/transport.js';\nimport { asSessionId, type SessionId } from '../contracts/ids.js';\nimport { readRequestBody } from './HttpHelpers.js';\nimport { runWithContext } from '../context/RequestContext.js';\nimport {\n\tAcceptedWorkTracker,\n\tLifecycleFailureReporter,\n\tPreDispatchTracker,\n\tResponseFinalizer,\n\ttype PreDispatchLease,\n} from './HttpRequestLifecycle.js';\n\n/**\n * MCP Streamable HTTP transport options extending base TransportOptions.\n */\nexport interface StreamableHttpTransportOptions extends TransportOptions {\n\t/**\n\t * Path for the MCP endpoint\n\t * @default '/mcp'\n\t */\n\tpath?: string;\n\n\t/**\n\t * Metrics collector for transport telemetry\n\t */\n\tmetrics?: IMetrics;\n\n\t/**\n\t * Prometheus metrics provider function\n\t */\n\tmetricsProvider?: () => string;\n\n\t/**\n\t * Enable stateful session-based mode.\n\t * When true, sessions are tracked via Mcp-Session-Id header.\n\t * When false, each request is processed independently (stateless).\n\t * @default true\n\t */\n\tstateful?: boolean;\n\n\t/**\n\t * Custom session ID generator function.\n\t * @default () => randomUUID()\n\t */\n\tsessionIdGenerator?: () => string;\n\n\t/**\n\t * Enable request body size limit\n\t * @default true\n\t */\n\tenableBodySizeLimit?: boolean;\n\n\t/**\n\t * Maximum request body size in bytes\n\t * @default 10485760 (10MB)\n\t */\n\tmaxBodySize?: number;\n\n\t/**\n\t * Request timeout in milliseconds\n\t * @default 30000 (30 seconds)\n\t */\n\trequestTimeout?: number;\n\n\t/** Maximum retained sessions when idle expiry is enabled. */\n\tmaxSessions?: number;\n\n\t/** Idle duration before an unpinned session expires. */\n\tsessionIdleTimeoutMs?: number;\n\n\t/** Interval between idle-session sweeps. */\n\tsessionSweepIntervalMs?: number;\n}\n\ntype SessionRetentionOptions = {\n\treadonly maxSessions: number;\n\treadonly sessionIdleTimeoutMs: number;\n\treadonly sessionSweepIntervalMs: number;\n};\n\nfunction parseSessionRetentionOptions(\n\toptions: StreamableHttpTransportOptions\n): SessionRetentionOptions | null {\n\tconst { maxSessions, sessionIdleTimeoutMs, sessionSweepIntervalMs } = options;\n\tif (\n\t\tmaxSessions === undefined &&\n\t\tsessionIdleTimeoutMs === undefined &&\n\t\tsessionSweepIntervalMs === undefined\n\t) {\n\t\treturn null;\n\t}\n\tif (\n\t\tmaxSessions === undefined ||\n\t\t!Number.isFinite(maxSessions) ||\n\t\t!Number.isInteger(maxSessions) ||\n\t\tmaxSessions <= 0 ||\n\t\tsessionIdleTimeoutMs === undefined ||\n\t\t!Number.isFinite(sessionIdleTimeoutMs) ||\n\t\t!Number.isInteger(sessionIdleTimeoutMs) ||\n\t\tsessionIdleTimeoutMs <= 0 ||\n\t\tsessionSweepIntervalMs === undefined ||\n\t\t!Number.isFinite(sessionSweepIntervalMs) ||\n\t\t!Number.isInteger(sessionSweepIntervalMs) ||\n\t\tsessionSweepIntervalMs <= 0\n\t) {\n\t\tthrow new TypeError(\n\t\t\t'maxSessions, sessionIdleTimeoutMs, and sessionSweepIntervalMs must be provided together as positive integers'\n\t\t);\n\t}\n\treturn {\n\t\tmaxSessions,\n\t\tsessionIdleTimeoutMs,\n\t\tsessionSweepIntervalMs,\n\t};\n}\n\n/**\n * Internal session state for stateful mode.\n */\ninterface SessionState {\n\t/** Unique session identifier */\n\tid: SessionId;\n\t/** Timestamp when the session was created */\n\tcreatedAt: number;\n\t/** Timestamp of the last activity */\n\tlastActivityAt: number;\n\tacceptedPostCount: number;\n}\n\ntype SessionError = {\n\treadonly statusCode: number;\n\treadonly code: number;\n\treadonly message: string;\n};\n\ntype AcceptedPostSession = {\n\treadonly id: string | undefined;\n\treadonly state: SessionState | null;\n};\n\n/**\n * Streamable HTTP Transport for MCP server.\n *\n * This transport implements the MCP Streamable HTTP specification,\n * providing JSON-RPC over HTTP with optional session management.\n *\n * @remarks\n * **Security Features (inherited from BaseTransport):**\n * - Session ID validation (alphanumeric, max 64 chars)\n * - Query parameter sanitization (whitelist allowed keys)\n * - Rate limiting per IP (configurable, default 100 req/min)\n * - CORS origin validation\n * - Host header validation\n *\n * **MCP Streamable HTTP Spec Compliance:**\n * - POST /mcp — JSON-RPC method calls\n * - Mcp-Session-Id header for session management\n * - Content-Type: application/json for JSON-RPC responses\n *\n * **HTTP Status Code Mapping:**\n * - 200: Success (JSON-RPC response)\n * - 202: Accepted (JSON-RPC notification, no response body)\n * - 204: CORS Preflight (empty body)\n * - 400: Bad Request (invalid JSON, invalid session ID)\n * - 403: Forbidden (invalid CORS, invalid host)\n * - 404: Not Found\n * - 405: Method Not Allowed\n * - 413: Payload Too Large\n * - 429: Too Many Requests\n * - 500: Internal Server Error\n * - 503: Server Not Ready / Shutting Down\n */\nexport class StreamableHttpTransport extends BaseTransport implements ITransport {\n\tget kind(): TransportKind {\n\t\treturn 'streamable-http';\n\t}\n\tprivate _server: Server | null = null;\n\tprivate _mcpServer: McpServer | null = null;\n\tprivate _path: string;\n\tprivate _stateful: boolean;\n\tprivate _sessionIdGenerator: () => string;\n\tprivate _sessions: Map<SessionId, SessionState> = new Map();\n\tprivate _requestCount: number = 0;\n\tprivate _activeRequests: number = 0;\n\tprivate _bodySizeLimitEnabled: boolean;\n\tprivate _maxBodySize: number;\n\tprivate _requestTimeout: number;\n\tprivate _metrics?: IMetrics;\n\tprivate _metricsProvider: (() => string) | null;\n\tprivate readonly _lifecycleFailureReporter: LifecycleFailureReporter;\n\tprivate readonly _acceptedWork: AcceptedWorkTracker;\n\tprivate readonly _preDispatch = new PreDispatchTracker();\n\tprivate readonly _sessionRetention: SessionRetentionOptions | null;\n\tprivate _sessionSweepIntervalId: NodeJS.Timeout | null = null;\n\tprivate _stopPromise: Promise<void> | null = null;\n\n\tconstructor(options: StreamableHttpTransportOptions = {}) {\n\t\tconst sessionRetention = parseSessionRetentionOptions(options);\n\t\tsuper(options);\n\n\t\tthis._path = options.path ?? '/mcp';\n\t\tthis._stateful = options.stateful ?? true;\n\t\tthis._sessionIdGenerator = options.sessionIdGenerator ?? (() => randomUUID());\n\t\tthis._bodySizeLimitEnabled = options.enableBodySizeLimit ?? true;\n\t\tthis._maxBodySize = options.maxBodySize ?? 10 * 1024 * 1024;\n\t\tthis._requestTimeout = options.requestTimeout ?? 30000;\n\t\tthis._metrics = options.metrics;\n\t\tthis._metricsProvider = options.metricsProvider ?? null;\n\t\tthis._sessionRetention = sessionRetention;\n\t\tthis._lifecycleFailureReporter = new LifecycleFailureReporter((error) => {\n\t\t\tthis.log('error', 'Streamable HTTP request lifecycle failed', {\n\t\t\t\terror: getErrorMessage(error),\n\t\t\t});\n\t\t});\n\t\tthis._acceptedWork = new AcceptedWorkTracker(this._lifecycleFailureReporter);\n\t\tif (this._stateful && sessionRetention) {\n\t\t\tthis._sessionSweepIntervalId = setInterval(\n\t\t\t\t() => this._sweepExpiredSessions(),\n\t\t\t\tsessionRetention.sessionSweepIntervalMs\n\t\t\t);\n\t\t\tthis._sessionSweepIntervalId.unref();\n\t\t}\n\t}\n\n\tprivate _requireMcpServer(): McpServer {\n\t\tif (!this._mcpServer) {\n\t\t\tthrow new Error('MCP server not initialized. Did you call connect()?');\n\t\t}\n\t\treturn this._mcpServer;\n\t}\n\n\t/**\n\t * Get number of active sessions (stateful) or active requests (stateless).\n\t */\n\tget clientCount(): number {\n\t\treturn this._stateful ? this._sessions.size : this._activeRequests;\n\t}\n\n\t/**\n\t * Get the total number of requests handled.\n\t */\n\tget requestCount(): number {\n\t\treturn this._requestCount;\n\t}\n\n\t/**\n\t * Connects MCP server to this transport and starts listening.\n\t */\n\tasync connect(mcpServer: McpServer): Promise<void> {\n\t\tthis._mcpServer = mcpServer;\n\t\tconst server = createServer((req, res) => {\n\t\t\tconst responseFinalizer = new ResponseFinalizer(res, this._lifecycleFailureReporter);\n\t\t\tvoid this._handleRequest(req, res).catch((error: unknown) => {\n\t\t\t\tthis._lifecycleFailureReporter.report(error);\n\t\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\t\tthis._sendJsonRpcError(response, 500, -32603, 'Internal error', null, {\n\t\t\t\t\t\tdata: getErrorMessage(error),\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t});\n\t\t});\n\t\tthis._server = server;\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst cleanup = (): void => {\n\t\t\t\tserver.off('error', onError);\n\t\t\t\tserver.off('listening', onListening);\n\t\t\t};\n\t\t\tconst onError = (error: Error): void => {\n\t\t\t\tcleanup();\n\t\t\t\treject(error);\n\t\t\t};\n\t\t\tconst onListening = (): void => {\n\t\t\t\tcleanup();\n\t\t\t\tthis.log(\n\t\t\t\t\t'info',\n\t\t\t\t\t`Streamable HTTP transport listening on http://${this._host}:${this._port}`\n\t\t\t\t);\n\t\t\t\tresolve();\n\t\t\t};\n\t\t\tserver.once('error', onError);\n\t\t\tserver.once('listening', onListening);\n\t\t\ttry {\n\t\t\t\tserver.listen(this._port, this._host);\n\t\t\t} catch (error) {\n\t\t\t\tcleanup();\n\t\t\t\treject(error);\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate _recordRequestMetrics(res: ServerResponse): void {\n\t\tconst startTime = Date.now();\n\t\tthis._metrics?.counter(\n\t\t\t'streamable_http_requests_total',\n\t\t\t1,\n\t\t\t{},\n\t\t\t'Total Streamable HTTP transport requests'\n\t\t);\n\t\tres.once('finish', () => {\n\t\t\tconst durationSeconds = (Date.now() - startTime) / 1000;\n\t\t\tthis._metrics?.histogram('streamable_http_request_duration_seconds', durationSeconds, {});\n\t\t});\n\t}\n\n\t/**\n\t * Route and handle incoming HTTP requests.\n\t */\n\tprivate async _handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {\n\t\tthis._recordRequestMetrics(res);\n\n\t\t// Host validation\n\t\tif (!this.validateHostHeader(req)) {\n\t\t\tthis._sendJsonRpcError(res, 403, -32000, 'Forbidden - invalid host header');\n\t\t\treturn;\n\t\t}\n\n\t\t// Shutdown check\n\t\tif (this.isShuttingDown) {\n\t\t\tthis._sendJsonRpcError(res, 503, -32603, 'Server is shutting down');\n\t\t\treturn;\n\t\t}\n\n\t\t// Rate limiting\n\t\tconst clientIp = this.getClientIp(req);\n\t\tif (this.checkRateLimit(clientIp)) {\n\t\t\tres.setHeader('Retry-After', '60');\n\t\t\tthis._sendJsonRpcError(res, 429, -32000, 'Too many requests');\n\t\t\treturn;\n\t\t}\n\n\t\t// CORS validation\n\t\tif (!this.validateCorsOrigin(req)) {\n\t\t\tthis._sendJsonRpcError(res, 403, -32000, 'Forbidden - invalid origin');\n\t\t\treturn;\n\t\t}\n\n\t\tthis.setCorsHeaders(res);\n\n\t\t// CORS preflight\n\t\tif (req.method === 'OPTIONS') {\n\t\t\tres.setHeader('Access-Control-Allow-Headers', 'Content-Type, Mcp-Session-Id');\n\t\t\tres.writeHead(204);\n\t\t\tres.end();\n\t\t\treturn;\n\t\t}\n\n\t\t// Parse URL path\n\t\tconst urlPath = req.url?.split('?')[0] ?? '/';\n\n\t\t// Metrics endpoint\n\t\tif (req.method === 'GET' && urlPath === '/metrics') {\n\t\t\tthis._handleMetrics(res);\n\t\t\treturn;\n\t\t}\n\n\t\t// Health check (liveness)\n\t\tif (req.method === 'GET' && urlPath === '/health') {\n\t\t\tthis._handleHealthCheck(res);\n\t\t\treturn;\n\t\t}\n\n\t\t// Readiness check\n\t\tif (req.method === 'GET' && urlPath === '/ready') {\n\t\t\tawait this._handleReadinessCheck(res);\n\t\t\treturn;\n\t\t}\n\n\t\t// MCP endpoint routing\n\t\tif (urlPath === this._path) {\n\t\t\tif (req.method === 'POST') {\n\t\t\t\tconst lease = this._preDispatch.acquire();\n\t\t\t\tif (!lease) {\n\t\t\t\t\tconst responseFinalizer = new ResponseFinalizer(res, this._lifecycleFailureReporter);\n\t\t\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\t\t\tthis._sendJsonRpcError(response, 503, -32603, 'Server is shutting down');\n\t\t\t\t\t});\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tawait this._handleMcpPost(req, res, lease);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tres.writeHead(405, { 'Content-Type': 'application/json', Allow: 'POST' });\n\t\t\tres.end(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tjsonrpc: '2.0',\n\t\t\t\t\tid: null,\n\t\t\t\t\terror: { code: -32601, message: 'Method not allowed' },\n\t\t\t\t})\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\t// 404 for unknown paths\n\t\tthis._sendJsonRpcError(res, 404, -32601, 'Not Found');\n\t}\n\n\t/**\n\t * Send a JSON-RPC error response.\n\t */\n\tprivate _sendJsonRpcError(\n\t\tres: ServerResponse,\n\t\tstatusCode: number,\n\t\tcode: number,\n\t\tmessage: string,\n\t\tid: unknown = null,\n\t\textra?: Record<string, unknown>\n\t): void {\n\t\tconst error: Record<string, unknown> = { code, message };\n\t\tif (extra) {\n\t\t\tObject.assign(error, extra);\n\t\t}\n\t\tres.writeHead(statusCode, { 'Content-Type': 'application/json' });\n\t\tres.end(JSON.stringify({ jsonrpc: '2.0', id, error }));\n\t}\n\n\tprivate _parseJsonRpcRequest(\n\t\tbody: string,\n\t\tresponseFinalizer: ResponseFinalizer\n\t): Parameters<McpServer['receive']>[0] | null {\n\t\tlet rawBody: unknown;\n\t\ttry {\n\t\t\trawBody = JSON.parse(body) as unknown;\n\t\t} catch {\n\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\tthis._sendJsonRpcError(response, 200, -32700, 'Parse error');\n\t\t\t});\n\t\t\treturn null;\n\t\t}\n\n\t\tconst parseResult = safeParse(JsonRpcRequestSchema, rawBody);\n\t\tconst rawId =\n\t\t\trawBody && typeof rawBody === 'object' && 'id' in rawBody\n\t\t\t\t? ((rawBody as { id?: unknown }).id ?? null)\n\t\t\t\t: null;\n\t\tif (!parseResult.success) {\n\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\tthis._sendJsonRpcError(\n\t\t\t\t\tresponse,\n\t\t\t\t\t200,\n\t\t\t\t\t-32600,\n\t\t\t\t\t'Invalid Request',\n\t\t\t\t\trawId as string | number | null,\n\t\t\t\t\t{ data: parseResult.issues }\n\t\t\t\t);\n\t\t\t});\n\t\t\treturn null;\n\t\t}\n\t\treturn parseResult.output as Parameters<McpServer['receive']>[0];\n\t}\n\n\t/**\n\t * Handle POST /mcp — JSON-RPC method calls.\n\t *\n\t * Per the MCP Streamable HTTP spec:\n\t * - Accepts JSON-RPC request bodies\n\t * - Returns Mcp-Session-Id header for new sessions (stateful mode)\n\t * - Validates Mcp-Session-Id for existing sessions (stateful mode)\n\t * - Content-Type: application/json for responses\n\t */\n\tprivate async _handleMcpPost(\n\t\treq: IncomingMessage,\n\t\tres: ServerResponse,\n\t\tlease: PreDispatchLease\n\t): Promise<void> {\n\t\tthis._requestCount++;\n\t\tthis._activeRequests++;\n\t\tconst responseFinalizer = new ResponseFinalizer(res, this._lifecycleFailureReporter);\n\t\tlet acceptedSession: SessionState | null = null;\n\t\tconst onAborted = (): void => lease.cancel('peer');\n\t\treq.once('aborted', onAborted);\n\n\t\tconst timeout = setTimeout(() => {\n\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\tthis._sendJsonRpcError(response, 500, -32603, 'Request timeout');\n\t\t\t});\n\t\t\tlease.cancel('timeout');\n\t\t}, this._requestTimeout);\n\t\ttry {\n\t\t\t// Read request body with size limit\n\t\t\tconst body = await readRequestBody(\n\t\t\t\treq,\n\t\t\t\tthis._bodySizeLimitEnabled ? this._maxBodySize : 0,\n\t\t\t\tlease.signal\n\t\t\t);\n\t\t\tif (body === null) {\n\t\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\t\tthis._sendJsonRpcError(response, 413, -32000, 'Request body too large');\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst jsonRpcRequest = this._parseJsonRpcRequest(body, responseFinalizer);\n\t\t\tif (jsonRpcRequest === null) return;\n\n\t\t\tconst session = this._acceptPostSession(req, responseFinalizer);\n\t\t\tif (!session) return;\n\t\t\tacceptedSession = session.state;\n\n\t\t\t// Check if MCP server is ready\n\t\t\tif (!this._mcpServer) {\n\t\t\t\tconst requestId = 'id' in jsonRpcRequest ? (jsonRpcRequest.id ?? null) : null;\n\t\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\t\tthis._sendJsonRpcError(response, 503, -32603, 'Server not ready', requestId);\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\treq.off('aborted', onAborted);\n\t\t\tconst acceptedWork = this._acceptedWork.transfer(lease, () =>\n\t\t\t\tthis._receiveAndFinalize(jsonRpcRequest, session.id, responseFinalizer).catch(\n\t\t\t\t\t(error: unknown) => {\n\t\t\t\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\t\t\t\tthis._sendJsonRpcError(response, 200, -32603, 'Internal error', null, {\n\t\t\t\t\t\t\t\tdata: getErrorMessage(error),\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t)\n\t\t\t);\n\t\t\tawait acceptedWork;\n\t\t} catch (error) {\n\t\t\tif (lease.signal.aborted) {\n\t\t\t\tif (lease.cancellationReason === 'shutdown') {\n\t\t\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\t\t\tthis._sendJsonRpcError(response, 503, -32603, 'Server is shutting down');\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\tthis._sendJsonRpcError(response, 200, -32603, 'Internal error', null, {\n\t\t\t\t\tdata: getErrorMessage(error),\n\t\t\t\t});\n\t\t\t});\n\t\t} finally {\n\t\t\treq.off('aborted', onAborted);\n\t\t\tlease.release();\n\t\t\tif (acceptedSession) acceptedSession.acceptedPostCount--;\n\t\t\tclearTimeout(timeout);\n\t\t\tthis._activeRequests--;\n\t\t}\n\t}\n\n\tprivate _acceptPostSession(\n\t\treq: IncomingMessage,\n\t\tresponseFinalizer: ResponseFinalizer\n\t): AcceptedPostSession | null {\n\t\tif (!this._stateful) return { id: undefined, state: null };\n\t\tif (this.isShuttingDown) {\n\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\tthis._sendJsonRpcError(response, 503, -32603, 'Server is shutting down');\n\t\t\t});\n\t\t\treturn null;\n\t\t}\n\t\tconst session = this._resolveSession(req);\n\t\tif (!('state' in session)) {\n\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\tthis._sendJsonRpcError(response, session.statusCode, session.code, session.message);\n\t\t\t});\n\t\t\treturn null;\n\t\t}\n\t\tsession.state.acceptedPostCount++;\n\t\treturn { id: session.id, state: session.state };\n\t}\n\n\tprivate async _receiveAndFinalize(\n\t\tjsonRpcRequest: Parameters<McpServer['receive']>[0],\n\t\tsessionId: string | undefined,\n\t\tresponseFinalizer: ResponseFinalizer\n\t): Promise<void> {\n\t\tconst owner = this._stateful ? (sessionId ?? randomUUID()) : randomUUID();\n\t\tconst response = await runWithContext({ requestId: randomUUID(), owner }, () =>\n\t\t\tthis._requireMcpServer().receive(jsonRpcRequest, { sessionInfo: {} })\n\t\t);\n\t\tconst responseHeaders: Record<string, string> = { 'Content-Type': 'application/json' };\n\t\tif (sessionId) responseHeaders['Mcp-Session-Id'] = sessionId;\n\n\t\tresponseFinalizer.finalize((httpResponse) => {\n\t\t\tif (response) {\n\t\t\t\thttpResponse.writeHead(200, responseHeaders);\n\t\t\t\thttpResponse.end(JSON.stringify(response));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (sessionId) httpResponse.setHeader('Mcp-Session-Id', sessionId);\n\t\t\thttpResponse.writeHead(202);\n\t\t\thttpResponse.end();\n\t\t});\n\t}\n\n\t/**\n\t * Resolve or create a session for a stateful request.\n\t *\n\t * @returns Session ID string on success, or the response error to finalize.\n\t */\n\tprivate _resolveSession(\n\t\treq: IncomingMessage\n\t): { readonly id: SessionId; readonly state: SessionState } | SessionError {\n\t\tconst headerSessionId = this._getSessionIdFromHeader(req);\n\n\t\tif (headerSessionId) {\n\t\t\t// Validate format\n\t\t\tif (!this.validateSessionId(headerSessionId)) {\n\t\t\t\treturn {\n\t\t\t\t\tstatusCode: 400,\n\t\t\t\t\tcode: -32600,\n\t\t\t\t\tmessage: 'Invalid Mcp-Session-Id format',\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// Check if session exists\n\t\t\tconst session = this._sessions.get(asSessionId(headerSessionId));\n\t\t\tif (session) {\n\t\t\t\tsession.lastActivityAt = Date.now();\n\t\t\t\treturn { id: session.id, state: session };\n\t\t\t}\n\n\t\t\t// Unknown session ID — per spec, return 404\n\t\t\treturn { statusCode: 404, code: -32001, message: 'Session not found' };\n\t\t}\n\n\t\t// No session header — create new session\n\t\tif (this._sessionRetention && this._sessions.size >= this._sessionRetention.maxSessions) {\n\t\t\treturn { statusCode: 503, code: -32000, message: 'Session capacity reached' };\n\t\t}\n\t\tconst newSessionId = asSessionId(this._sessionIdGenerator());\n\t\tconst now = Date.now();\n\t\tconst sessionState: SessionState = {\n\t\t\tid: newSessionId,\n\t\t\tcreatedAt: now,\n\t\t\tlastActivityAt: now,\n\t\t\tacceptedPostCount: 0,\n\t\t};\n\t\tthis._sessions.set(newSessionId, sessionState);\n\t\tthis.log('info', `New session created: ${newSessionId}`);\n\t\tthis._updateSessionMetrics();\n\n\t\treturn { id: newSessionId, state: sessionState };\n\t}\n\n\tprivate _sweepExpiredSessions(): void {\n\t\tif (!this._sessionRetention) return;\n\t\tconst now = Date.now();\n\t\tfor (const [sessionId, session] of this._sessions) {\n\t\t\tif (\n\t\t\t\tsession.acceptedPostCount === 0 &&\n\t\t\t\tnow - session.lastActivityAt >= this._sessionRetention.sessionIdleTimeoutMs\n\t\t\t) {\n\t\t\t\tthis._disposeSession(sessionId);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate _disposeSession(sessionId: SessionId): void {\n\t\tif (!this._sessions.delete(sessionId)) return;\n\t\tthis._updateSessionMetrics();\n\t}\n\n\tprivate _stopSessionSweep(): void {\n\t\tconst interval = this._sessionSweepIntervalId;\n\t\tif (!interval) return;\n\t\tinterval.unref();\n\t\tclearInterval(interval);\n\t\tthis._sessionSweepIntervalId = null;\n\t}\n\n\t/**\n\t * Extract Mcp-Session-Id from request headers.\n\t */\n\tprivate _getSessionIdFromHeader(req: IncomingMessage): string | undefined {\n\t\tconst value = req.headers['mcp-session-id'];\n\t\tif (typeof value === 'string' && value.length > 0) {\n\t\t\treturn value;\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Handle GET /metrics endpoint.\n\t */\n\tprivate _handleMetrics(res: ServerResponse): void {\n\t\tif (!this._metricsProvider) {\n\t\t\tres.writeHead(404, { 'Content-Type': 'text/plain' });\n\t\t\tres.end('Not Found');\n\t\t\treturn;\n\t\t}\n\n\t\tres.writeHead(200, { 'Content-Type': 'text/plain; version=0.0.4; charset=utf-8' });\n\t\tres.end(this._metricsProvider());\n\t}\n\n\t/**\n\t * Handle GET /health — Liveness check.\n\t */\n\tprivate _handleHealthCheck(res: ServerResponse): void {\n\t\tconst healthData: Record<string, unknown> = {\n\t\t\tstatus: 'healthy',\n\t\t\trequests: this._requestCount,\n\t\t\tsessions: this._sessions.size,\n\t\t\ttransport: 'streamable-http',\n\t\t};\n\t\tif (this._healthChecker) {\n\t\t\tconst liveness = this._healthChecker.checkLiveness();\n\t\t\thealthData.liveness = liveness;\n\t\t}\n\t\tres.writeHead(200, { 'Content-Type': 'application/json' });\n\t\tres.end(JSON.stringify(healthData));\n\t}\n\n\t/**\n\t * Handle GET /ready — Readiness check.\n\t */\n\tprivate async _handleReadinessCheck(res: ServerResponse): Promise<void> {\n\t\tif (this._healthChecker) {\n\t\t\tconst readiness = await this._healthChecker.checkReadiness();\n\t\t\tconst statusCode = readiness.status === 'ok' ? 200 : 503;\n\t\t\tres.writeHead(statusCode, { 'Content-Type': 'application/json' });\n\t\t\tres.end(JSON.stringify(readiness));\n\t\t} else {\n\t\t\tres.writeHead(200, { 'Content-Type': 'application/json' });\n\t\t\tres.end(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tstatus: 'ok',\n\t\t\t\t\ttimestamp: new Date().toISOString(),\n\t\t\t\t\tcomponents: {},\n\t\t\t\t})\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Update session-related metrics.\n\t */\n\tprivate _updateSessionMetrics(): void {\n\t\tthis._metrics?.gauge(\n\t\t\t'streamable_http_active_sessions',\n\t\t\tthis._sessions.size,\n\t\t\t{},\n\t\t\t'Active Streamable HTTP sessions'\n\t\t);\n\t}\n\n\t/**\n\t * Stop transport server with graceful shutdown.\n\t *\n\t * @param timeout - Maximum time before force-closing HTTP connections (default: 30s)\n\t */\n\tstop(timeout?: number): Promise<void> {\n\t\tif (this._stopPromise) return this._stopPromise;\n\t\tthis._isShuttingDown = true;\n\t\tthis._preDispatch.closeAdmission('shutdown');\n\t\tthis._stopRateLimitCleanup();\n\t\tthis._stopSessionSweep();\n\n\t\tconst shutdownTimeout = timeout ?? 30000;\n\n\t\tfor (const sessionId of this._sessions.keys()) {\n\t\t\tthis._disposeSession(sessionId);\n\t\t}\n\n\t\tconst server = this._server;\n\t\tconst serverClosed = new Promise<void>((resolve, reject) => {\n\t\t\tif (!server?.listening) {\n\t\t\t\tresolve();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet settled = false;\n\t\t\tlet forceClose: NodeJS.Timeout | null = null;\n\t\t\tserver.close((error) => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tif (forceClose) clearTimeout(forceClose);\n\t\t\t\tif (error) {\n\t\t\t\t\treject(error);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tresolve();\n\t\t\t});\n\t\t\tif (settled) return;\n\t\t\tforceClose = setTimeout(() => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tthis.log('warn', 'Streamable HTTP transport force-closing after timeout');\n\t\t\t\tserver.closeAllConnections();\n\t\t\t\tresolve();\n\t\t\t}, shutdownTimeout);\n\t\t});\n\t\tconst stopPromise = Promise.all([\n\t\t\tserverClosed,\n\t\t\tthis._preDispatch.join().then(() => this._acceptedWork.join()),\n\t\t]).then(() => {\n\t\t\tthis.log('info', 'Streamable HTTP transport stopped');\n\t\t});\n\t\tthis._stopPromise = stopPromise;\n\t\tvoid stopPromise.catch(() => {\n\t\t\tif (this._stopPromise === stopPromise) this._stopPromise = null;\n\t\t});\n\t\treturn stopPromise;\n\t}\n}\n\n/**\n * Create a Streamable HTTP transport with given options.\n *\n * @param options - Transport configuration\n * @returns A configured Streamable HTTP transport\n *\n * @example\n * ```typescript\n * const transport = createStreamableHttpTransport({ port: 3000, stateful: true });\n * await transport.connect(server);\n * ```\n */\nexport function createStreamableHttpTransport(\n\toptions: StreamableHttpTransportOptions = {}\n): StreamableHttpTransport {\n\treturn new StreamableHttpTransport(options);\n}\n"],"names":["parseSessionRetentionOptions","options","maxSessions","sessionIdleTimeoutMs","sessionSweepIntervalMs","undefined","Number","TypeError","StreamableHttpTransport","BaseTransport","Map","PreDispatchTracker","sessionRetention","randomUUID","LifecycleFailureReporter","error","getErrorMessage","AcceptedWorkTracker","setInterval","Error","mcpServer","server","createServer","req","res","responseFinalizer","ResponseFinalizer","response","Promise","resolve","reject","cleanup","onError","onListening","startTime","Date","durationSeconds","clientIp","urlPath","lease","JSON","statusCode","code","message","id","extra","Object","body","rawBody","parseResult","safeParse","JsonRpcRequestSchema","rawId","acceptedSession","onAborted","timeout","setTimeout","readRequestBody","jsonRpcRequest","session","requestId","acceptedWork","clearTimeout","sessionId","owner","runWithContext","responseHeaders","httpResponse","headerSessionId","asSessionId","newSessionId","now","sessionState","interval","clearInterval","value","healthData","liveness","readiness","shutdownTimeout","serverClosed","settled","forceClose","stopPromise","createStreamableHttpTransport"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"SkillWatcher.d.ts","sourceRoot":"","sources":["../../src/watchers/SkillWatcher.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAElE,6EAA6E;AAC7E,qBAAa,YAAY;IAUvB,OAAO,CAAC,QAAQ,CAAC,cAAc;IAThC,OAAO,CAAC,QAAQ,CAA0B;IAC1C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAC9C,OAAO,CAAC,eAAe,CAA8B;IACrD,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,YAAY,CAA8B;gBAGhC,cAAc,EAAE,aAAa,EAC9C,MAAM,CAAC,EAAE,MAAM,EACf,SAAS,GAAE,SAAS,MAAM,EAA0D;IAMrF,OAAO,CAAC,iBAAiB;IAWzB,OAAO,CAAC,aAAa;IAcrB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,gBAAgB;YAUV,eAAe;IAc7B,OAAO,CAAC,IAAI;IAIZ,6DAA6D;IACtD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI7B,sEAAsE;IAC/D,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAS5B"}
1
+ {"version":3,"file":"SkillWatcher.d.ts","sourceRoot":"","sources":["../../src/watchers/SkillWatcher.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAElE,6EAA6E;AAC7E,qBAAa,YAAY;IAUvB,OAAO,CAAC,QAAQ,CAAC,cAAc;IAThC,OAAO,CAAC,QAAQ,CAA0B;IAC1C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAC9C,OAAO,CAAC,eAAe,CAA8B;IACrD,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,YAAY,CAA8B;gBAGhC,cAAc,EAAE,aAAa,EAC9C,MAAM,CAAC,EAAE,MAAM,EACf,SAAS,GAAE,SAAS,MAAM,EAA0D;IAMrF,OAAO,CAAC,iBAAiB;IAWzB,OAAO,CAAC,aAAa;IAcrB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,gBAAgB;YAUV,eAAe;IAc7B,OAAO,CAAC,IAAI;IAMZ,6DAA6D;IACtD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI7B,sEAAsE;IAC/D,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAS5B"}
@@ -73,7 +73,7 @@ class SkillWatcher {
73
73
  }
74
74
  }
75
75
  _log(message) {
76
- if ('true' === process.env.WATCHER_VERBOSE) this._logger.debug(`[Watcher] ${message}`);
76
+ if ('true' === process.env.TRACELATTICE_WATCHER_VERBOSE) this._logger.debug(`[Watcher] ${message}`);
77
77
  }
78
78
  ready() {
79
79
  return this._readyPromise;
@@ -1 +1 @@
1
- {"version":3,"file":"watchers/SkillWatcher.js","sources":["../../src/watchers/SkillWatcher.ts"],"sourcesContent":["import { watch, type FSWatcher } from 'chokidar';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { getErrorMessage } from '../errors.js';\nimport type { Logger } from '../logger/StructuredLogger.js';\nimport type { SkillRegistry } from '../registry/SkillRegistry.js';\n\n/** Watches skill discovery directories and serializes registry refreshes. */\nexport class SkillWatcher {\n\tprivate _watcher: FSWatcher | null = null;\n\tprivate readonly _logger: Logger;\n\tprivate readonly _readyPromise: Promise<void>;\n\tprivate _pendingRefresh: Promise<void> | null = null;\n\tprivate _refreshQueued = false;\n\tprivate _stopped = false;\n\tprivate _stopPromise: Promise<void> | null = null;\n\n\tconstructor(\n\t\tprivate readonly _skillRegistry: SkillRegistry,\n\t\tlogger?: Logger,\n\t\twatchDirs: readonly string[] = ['.claude/skills', join(homedir(), '.claude/skills')]\n\t) {\n\t\tthis._logger = logger ?? this._createNoopLogger();\n\t\tthis._readyPromise = this._setupWatcher(watchDirs);\n\t}\n\n\tprivate _createNoopLogger(): Logger {\n\t\treturn {\n\t\t\tinfo: () => {},\n\t\t\twarn: () => {},\n\t\t\terror: () => {},\n\t\t\tdebug: () => {},\n\t\t\tsetLevel: () => {},\n\t\t\tgetLevel: () => 'info',\n\t\t};\n\t}\n\n\tprivate _setupWatcher(watchDirs: readonly string[]): Promise<void> {\n\t\tif (watchDirs.length === 0) return Promise.resolve();\n\t\tconst watcher = watch([...watchDirs], {\n\t\t\tignored: [/node_modules/, /\\.DS_Store$/],\n\t\t\tpersistent: true,\n\t\t\tignoreInitial: true,\n\t\t});\n\t\tthis._watcher = watcher;\n\t\twatcher.on('add', (path) => this._handleEvent('added', path));\n\t\twatcher.on('change', (path) => this._handleEvent('modified', path));\n\t\twatcher.on('unlink', (path) => this._handleEvent('removed', path));\n\t\treturn new Promise((resolve) => watcher.on('ready', () => resolve()));\n\t}\n\n\tprivate _handleEvent(action: string, path: string): Promise<void> {\n\t\tif (this._stopped) return Promise.resolve();\n\t\tif (!path.includes('.DS_Store')) this._log(`Skill ${action}: ${path.split(/[/\\\\]/).pop()}`);\n\t\treturn this._scheduleRefresh(path);\n\t}\n\n\tprivate _scheduleRefresh(path: string): Promise<void> {\n\t\tthis._refreshQueued = true;\n\t\tif (!this._pendingRefresh) {\n\t\t\tthis._pendingRefresh = this._drainRefreshes(path).finally(() => {\n\t\t\t\tthis._pendingRefresh = null;\n\t\t\t});\n\t\t}\n\t\treturn this._pendingRefresh;\n\t}\n\n\tprivate async _drainRefreshes(path: string): Promise<void> {\n\t\twhile (this._refreshQueued) {\n\t\t\tthis._refreshQueued = false;\n\t\t\ttry {\n\t\t\t\tawait this._skillRegistry.refreshAsync();\n\t\t\t} catch (error) {\n\t\t\t\tthis._logger.error('Skill discovery refresh failed', {\n\t\t\t\t\tpath,\n\t\t\t\t\terror: getErrorMessage(error),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate _log(message: string): void {\n\t\tif (process.env.WATCHER_VERBOSE === 'true') this._logger.debug(`[Watcher] ${message}`);\n\t}\n\n\t/** Resolves when Chokidar has completed its initial scan. */\n\tpublic ready(): Promise<void> {\n\t\treturn this._readyPromise;\n\t}\n\n\t/** Closes Chokidar and joins any refresh accepted before shutdown. */\n\tpublic stop(): Promise<void> {\n\t\tif (this._stopPromise) return this._stopPromise;\n\t\tthis._stopped = true;\n\t\tconst watcher = this._watcher;\n\t\tconst pendingRefresh = this._pendingRefresh;\n\t\tthis._watcher = null;\n\t\tthis._stopPromise = Promise.all([watcher?.close(), pendingRefresh]).then(() => undefined);\n\t\treturn this._stopPromise;\n\t}\n}\n"],"names":["SkillWatcher","_skillRegistry","logger","watchDirs","join","homedir","Promise","watcher","watch","path","resolve","action","error","getErrorMessage","message","process","pendingRefresh","undefined"],"mappings":"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"}
1
+ {"version":3,"file":"watchers/SkillWatcher.js","sources":["../../src/watchers/SkillWatcher.ts"],"sourcesContent":["import { watch, type FSWatcher } from 'chokidar';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { getErrorMessage } from '../errors.js';\nimport type { Logger } from '../logger/StructuredLogger.js';\nimport type { SkillRegistry } from '../registry/SkillRegistry.js';\n\n/** Watches skill discovery directories and serializes registry refreshes. */\nexport class SkillWatcher {\n\tprivate _watcher: FSWatcher | null = null;\n\tprivate readonly _logger: Logger;\n\tprivate readonly _readyPromise: Promise<void>;\n\tprivate _pendingRefresh: Promise<void> | null = null;\n\tprivate _refreshQueued = false;\n\tprivate _stopped = false;\n\tprivate _stopPromise: Promise<void> | null = null;\n\n\tconstructor(\n\t\tprivate readonly _skillRegistry: SkillRegistry,\n\t\tlogger?: Logger,\n\t\twatchDirs: readonly string[] = ['.claude/skills', join(homedir(), '.claude/skills')]\n\t) {\n\t\tthis._logger = logger ?? this._createNoopLogger();\n\t\tthis._readyPromise = this._setupWatcher(watchDirs);\n\t}\n\n\tprivate _createNoopLogger(): Logger {\n\t\treturn {\n\t\t\tinfo: () => {},\n\t\t\twarn: () => {},\n\t\t\terror: () => {},\n\t\t\tdebug: () => {},\n\t\t\tsetLevel: () => {},\n\t\t\tgetLevel: () => 'info',\n\t\t};\n\t}\n\n\tprivate _setupWatcher(watchDirs: readonly string[]): Promise<void> {\n\t\tif (watchDirs.length === 0) return Promise.resolve();\n\t\tconst watcher = watch([...watchDirs], {\n\t\t\tignored: [/node_modules/, /\\.DS_Store$/],\n\t\t\tpersistent: true,\n\t\t\tignoreInitial: true,\n\t\t});\n\t\tthis._watcher = watcher;\n\t\twatcher.on('add', (path) => this._handleEvent('added', path));\n\t\twatcher.on('change', (path) => this._handleEvent('modified', path));\n\t\twatcher.on('unlink', (path) => this._handleEvent('removed', path));\n\t\treturn new Promise((resolve) => watcher.on('ready', () => resolve()));\n\t}\n\n\tprivate _handleEvent(action: string, path: string): Promise<void> {\n\t\tif (this._stopped) return Promise.resolve();\n\t\tif (!path.includes('.DS_Store')) this._log(`Skill ${action}: ${path.split(/[/\\\\]/).pop()}`);\n\t\treturn this._scheduleRefresh(path);\n\t}\n\n\tprivate _scheduleRefresh(path: string): Promise<void> {\n\t\tthis._refreshQueued = true;\n\t\tif (!this._pendingRefresh) {\n\t\t\tthis._pendingRefresh = this._drainRefreshes(path).finally(() => {\n\t\t\t\tthis._pendingRefresh = null;\n\t\t\t});\n\t\t}\n\t\treturn this._pendingRefresh;\n\t}\n\n\tprivate async _drainRefreshes(path: string): Promise<void> {\n\t\twhile (this._refreshQueued) {\n\t\t\tthis._refreshQueued = false;\n\t\t\ttry {\n\t\t\t\tawait this._skillRegistry.refreshAsync();\n\t\t\t} catch (error) {\n\t\t\t\tthis._logger.error('Skill discovery refresh failed', {\n\t\t\t\t\tpath,\n\t\t\t\t\terror: getErrorMessage(error),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate _log(message: string): void {\n\t\tif (process.env.TRACELATTICE_WATCHER_VERBOSE === 'true') {\n\t\t\tthis._logger.debug(`[Watcher] ${message}`);\n\t\t}\n\t}\n\n\t/** Resolves when Chokidar has completed its initial scan. */\n\tpublic ready(): Promise<void> {\n\t\treturn this._readyPromise;\n\t}\n\n\t/** Closes Chokidar and joins any refresh accepted before shutdown. */\n\tpublic stop(): Promise<void> {\n\t\tif (this._stopPromise) return this._stopPromise;\n\t\tthis._stopped = true;\n\t\tconst watcher = this._watcher;\n\t\tconst pendingRefresh = this._pendingRefresh;\n\t\tthis._watcher = null;\n\t\tthis._stopPromise = Promise.all([watcher?.close(), pendingRefresh]).then(() => undefined);\n\t\treturn this._stopPromise;\n\t}\n}\n"],"names":["SkillWatcher","_skillRegistry","logger","watchDirs","join","homedir","Promise","watcher","watch","path","resolve","action","error","getErrorMessage","message","process","pendingRefresh","undefined"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iworkforces/tracelattice",
3
- "version": "2.0.7",
3
+ "version": "2.1.1",
4
4
  "description": "Semantic Sequential Thinking Layer For AI Agents.",
5
5
  "type": "module",
6
6
  "main": "dist/lib.js",
@@ -87,7 +87,7 @@
87
87
  "@eslint/js": "^10.0.1",
88
88
  "@rsbuild/core": "^2.2.8",
89
89
  "@rslib/core": "^1.0.1",
90
- "@types/node": "^26.6.1",
90
+ "@types/node": "^26.6.2",
91
91
  "@typescript-eslint/eslint-plugin": "^8.70.0",
92
92
  "@typescript-eslint/parser": "^8.70.0",
93
93
  "@vitest/coverage-v8": "^5.0.1",
@@ -1 +0,0 @@
1
- {"version":3,"file":"contracts/PersistenceBackend.js","sources":["../../src/contracts/PersistenceBackend.ts"],"sourcesContent":["import type { ThoughtData } from '../core/thought.js';\nimport type { Edge } from '../core/graph/Edge.js';\nimport type { Summary } from '../core/compression/Summary.js';\nimport type { BranchId, SessionId } from './ids.js';\n\n/**\n * Persistence backend interface for storing thought history and branches.\n *\n * Implementations can store data in various formats (JSON files, SQLite, etc.)\n * while providing a unified API for HistoryManager.\n */\nexport interface PersistenceBackend {\n\t/**\n\t * Save a single thought to persistent storage.\n\t *\n\t * @param thought - The thought data to persist\n\t */\n\tsaveThought(thought: ThoughtData): Promise<void>;\n\n\t/**\n\t * Load all thoughts from persistent storage.\n\t * Returns thoughts in chronological order (oldest first).\n\t *\n\t * @returns Array of all persisted thoughts\n\t */\n\tloadHistory(): Promise<ThoughtData[]>;\n\n\t/**\n\t * Save all thoughts in a branch to persistent storage.\n\t *\n\t * @param branchId - The unique identifier for the branch\n\t * @param thoughts - Array of thoughts in branch\n\t */\n\tsaveBranch(branchId: BranchId, thoughts: ThoughtData[]): Promise<void>;\n\n\t/**\n\t * Delete one global branch without treating an empty branch as deletion.\n\t *\n\t * Optional on legacy backends; the complete scoped capability requires it.\n\t *\n\t * @param branchId - The branch identifier to delete\n\t */\n\tdeleteBranch?(branchId: BranchId): Promise<void>;\n\n\t/**\n\t * Load all thoughts for a specific branch.\n\t *\n\t * @param branchId - The unique identifier for the branch\n\t * @returns Array of thoughts in branch, or undefined if branch doesn't exist\n\t */\n\tloadBranch(branchId: BranchId): Promise<ThoughtData[] | undefined>;\n\n\t/**\n\t * List all branch IDs that are persisted.\n\t *\n\t * @returns Array of branch identifiers\n\t */\n\tlistBranches(): Promise<BranchId[]>;\n\n\t/**\n\t * Check if backend is healthy.\n\t * @returns Promise that resolves to true if healthy, false otherwise\n\t */\n\thealthy(): Promise<boolean>;\n\n\t/**\n\t * Clear all persisted data (history and branches).\n\t * Use with caution - this cannot be undone.\n\t */\n\tclear(): Promise<void>;\n\n\t/**\n\t * Close the backend and release resources.\n\t * Should be called during graceful shutdown to ensure data is flushed.\n\t */\n\tclose(): Promise<void>;\n\n\t/**\n\t * Save edges for a session, replacing any previously saved edges.\n\t *\n\t * @param sessionId - The session whose edges to persist\n\t * @param edges - Array of edges to save\n\t */\n\tsaveEdges(sessionId: SessionId, edges: readonly Edge[]): Promise<void>;\n\n\t/**\n\t * Load edges for a session from persistent storage.\n\t * Returns edges in chronological order (by createdAt ascending).\n\t * Returns empty array if no edges exist for the session.\n\t *\n\t * @param sessionId - The session whose edges to load\n\t * @returns Array of persisted edges, sorted by createdAt\n\t */\n\tloadEdges(sessionId: SessionId): Promise<Edge[]>;\n\n\t/**\n\t * List all session IDs that have persisted edge data.\n\t *\n\t * @returns Array of session identifiers with persisted edges\n\t */\n\tlistEdgeSessions(): Promise<SessionId[]>;\n\n\t/**\n\t * Save summaries for a session, replacing any previously saved summaries.\n\t *\n\t * @param sessionId - The session whose summaries to persist\n\t * @param summaries - Array of summaries to save\n\t */\n\tsaveSummaries(sessionId: SessionId, summaries: readonly Summary[]): Promise<void>;\n\n\t/**\n\t * Load summaries for a session from persistent storage.\n\t * Returns summaries in chronological order (by createdAt ascending).\n\t * Returns empty array if no summaries exist for the session.\n\t *\n\t * @param sessionId - The session whose summaries to load\n\t * @returns Array of persisted summaries, sorted by createdAt\n\t */\n\tloadSummaries(sessionId: SessionId): Promise<Summary[]>;\n}\n\n/** Session-scoped persistence operations implemented as one indivisible capability. */\nexport type SessionScopedPersistenceOperation =\n\t| 'saveThoughtForSession'\n\t| 'loadHistoryForSession'\n\t| 'saveBranchForSession'\n\t| 'deleteBranch'\n\t| 'deleteBranchForSession'\n\t| 'loadBranchForSession'\n\t| 'listBranchesForSession'\n\t| 'listSessions'\n\t| 'clearSession';\n\n/** Persistence contract for durable named-session isolation. */\nexport interface SessionScopedPersistenceBackend extends PersistenceBackend {\n\tdeleteBranch(branchId: BranchId): Promise<void>;\n\tsaveThoughtForSession(sessionId: SessionId, thought: ThoughtData): Promise<void>;\n\tloadHistoryForSession(sessionId: SessionId): Promise<ThoughtData[]>;\n\tsaveBranchForSession(\n\t\tsessionId: SessionId,\n\t\tbranchId: BranchId,\n\t\tthoughts: readonly ThoughtData[]\n\t): Promise<void>;\n\tdeleteBranchForSession(sessionId: SessionId, branchId: BranchId): Promise<void>;\n\tloadBranchForSession(\n\t\tsessionId: SessionId,\n\t\tbranchId: BranchId\n\t): Promise<ThoughtData[] | undefined>;\n\tlistBranchesForSession(sessionId: SessionId): Promise<BranchId[]>;\n\tlistSessions(): Promise<SessionId[]>;\n\tclearSession(sessionId: SessionId): Promise<void>;\n}\n\n/** Return whether a backend implements every session-scoped operation. */\nexport function supportsSessionScopedPersistence(\n\tbackend: PersistenceBackend\n): backend is SessionScopedPersistenceBackend {\n\treturn (\n\t\t'saveThoughtForSession' in backend &&\n\t\ttypeof backend.saveThoughtForSession === 'function' &&\n\t\t'loadHistoryForSession' in backend &&\n\t\ttypeof backend.loadHistoryForSession === 'function' &&\n\t\t'saveBranchForSession' in backend &&\n\t\ttypeof backend.saveBranchForSession === 'function' &&\n\t\t'deleteBranch' in backend &&\n\t\ttypeof backend.deleteBranch === 'function' &&\n\t\t'deleteBranchForSession' in backend &&\n\t\ttypeof backend.deleteBranchForSession === 'function' &&\n\t\t'loadBranchForSession' in backend &&\n\t\ttypeof backend.loadBranchForSession === 'function' &&\n\t\t'listBranchesForSession' in backend &&\n\t\ttypeof backend.listBranchesForSession === 'function' &&\n\t\t'listSessions' in backend &&\n\t\ttypeof backend.listSessions === 'function' &&\n\t\t'clearSession' in backend &&\n\t\ttypeof backend.clearSession === 'function'\n\t);\n}\n\nexport interface PersistenceConfig {\n\tenabled?: boolean;\n\tbackend?: 'file' | 'sqlite' | 'memory';\n\toptions?: {\n\t\tdataDir?: string;\n\t\tdbPath?: string;\n\t\tenableWAL?: boolean;\n\t\tmaxHistorySize?: number;\n\t\tpersistBranches?: boolean;\n\t};\n}\n"],"names":["supportsSessionScopedPersistence","backend"],"mappings":"AA0JO,SAASA,iCACfC,OAA2B;IAE3B,OACC,2BAA2BA,WAC3B,AAAyC,cAAzC,OAAOA,QAAQ,qBAAqB,IACpC,2BAA2BA,WAC3B,AAAyC,cAAzC,OAAOA,QAAQ,qBAAqB,IACpC,0BAA0BA,WAC1B,AAAwC,cAAxC,OAAOA,QAAQ,oBAAoB,IACnC,kBAAkBA,WAClB,AAAgC,cAAhC,OAAOA,QAAQ,YAAY,IAC3B,4BAA4BA,WAC5B,AAA0C,cAA1C,OAAOA,QAAQ,sBAAsB,IACrC,0BAA0BA,WAC1B,AAAwC,cAAxC,OAAOA,QAAQ,oBAAoB,IACnC,4BAA4BA,WAC5B,AAA0C,cAA1C,OAAOA,QAAQ,sBAAsB,IACrC,kBAAkBA,WAClB,AAAgC,cAAhC,OAAOA,QAAQ,YAAY,IAC3B,kBAAkBA,WAClB,AAAgC,cAAhC,OAAOA,QAAQ,YAAY;AAE7B"}
@@ -1,2 +0,0 @@
1
- export declare function importLegacyFileV1(sourceDataDir: string, destinationDataDir: string): Promise<void>;
2
- //# sourceMappingURL=FileLegacyImport.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"FileLegacyImport.d.ts","sourceRoot":"","sources":["../../src/persistence/FileLegacyImport.ts"],"names":[],"mappings":"AA4LA,wBAAsB,kBAAkB,CACvC,aAAa,EAAE,MAAM,EACrB,kBAAkB,EAAE,MAAM,GACxB,OAAO,CAAC,IAAI,CAAC,CA2Bf"}