@j0hanz/superfetch 2.0.1 → 2.1.0

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 (79) hide show
  1. package/README.md +120 -38
  2. package/dist/cache.d.ts +42 -0
  3. package/dist/cache.js +565 -0
  4. package/dist/config/env-parsers.d.ts +1 -0
  5. package/dist/config/env-parsers.js +12 -0
  6. package/dist/config/index.d.ts +7 -0
  7. package/dist/config/index.js +10 -3
  8. package/dist/config/types/content.d.ts +1 -0
  9. package/dist/config.d.ts +77 -0
  10. package/dist/config.js +261 -0
  11. package/dist/crypto.d.ts +2 -0
  12. package/dist/crypto.js +32 -0
  13. package/dist/errors.d.ts +10 -0
  14. package/dist/errors.js +28 -0
  15. package/dist/fetch.d.ts +40 -0
  16. package/dist/fetch.js +910 -0
  17. package/dist/http/base-middleware.d.ts +7 -0
  18. package/dist/http/base-middleware.js +143 -0
  19. package/dist/http/cors.d.ts +0 -5
  20. package/dist/http/cors.js +0 -6
  21. package/dist/http/download-routes.js +6 -2
  22. package/dist/http/error-handler.d.ts +2 -0
  23. package/dist/http/error-handler.js +55 -0
  24. package/dist/http/mcp-routes.js +2 -2
  25. package/dist/http/mcp-sessions.d.ts +3 -5
  26. package/dist/http/mcp-sessions.js +8 -8
  27. package/dist/http/server-tuning.d.ts +9 -0
  28. package/dist/http/server-tuning.js +45 -0
  29. package/dist/http/server.d.ts +0 -10
  30. package/dist/http/server.js +33 -333
  31. package/dist/http.d.ts +78 -0
  32. package/dist/http.js +1437 -0
  33. package/dist/index.js +3 -3
  34. package/dist/mcp.d.ts +3 -0
  35. package/dist/mcp.js +94 -0
  36. package/dist/observability.d.ts +16 -0
  37. package/dist/observability.js +78 -0
  38. package/dist/server.js +20 -5
  39. package/dist/services/cache.d.ts +1 -1
  40. package/dist/services/context.d.ts +2 -0
  41. package/dist/services/context.js +3 -0
  42. package/dist/services/extractor.d.ts +1 -0
  43. package/dist/services/extractor.js +28 -2
  44. package/dist/services/fetcher.d.ts +2 -0
  45. package/dist/services/fetcher.js +35 -14
  46. package/dist/services/logger.js +4 -1
  47. package/dist/services/telemetry.d.ts +19 -0
  48. package/dist/services/telemetry.js +43 -0
  49. package/dist/services/transform-worker-pool.d.ts +10 -3
  50. package/dist/services/transform-worker-pool.js +213 -184
  51. package/dist/tools/handlers/fetch-url.tool.js +8 -6
  52. package/dist/tools/index.d.ts +1 -0
  53. package/dist/tools/index.js +13 -1
  54. package/dist/tools/schemas.d.ts +2 -0
  55. package/dist/tools/schemas.js +8 -0
  56. package/dist/tools/utils/content-transform-core.d.ts +5 -0
  57. package/dist/tools/utils/content-transform-core.js +180 -0
  58. package/dist/tools/utils/content-transform-workers.d.ts +1 -0
  59. package/dist/tools/utils/content-transform-workers.js +1 -0
  60. package/dist/tools/utils/content-transform.d.ts +3 -5
  61. package/dist/tools/utils/content-transform.js +35 -148
  62. package/dist/tools/utils/raw-markdown.js +15 -1
  63. package/dist/tools.d.ts +104 -0
  64. package/dist/tools.js +421 -0
  65. package/dist/transform.d.ts +69 -0
  66. package/dist/transform.js +1509 -0
  67. package/dist/transformers/markdown.d.ts +4 -1
  68. package/dist/transformers/markdown.js +182 -53
  69. package/dist/utils/cancellation.d.ts +1 -0
  70. package/dist/utils/cancellation.js +18 -0
  71. package/dist/utils/code-language.d.ts +0 -9
  72. package/dist/utils/code-language.js +5 -5
  73. package/dist/utils/host-normalizer.d.ts +1 -0
  74. package/dist/utils/host-normalizer.js +37 -0
  75. package/dist/utils/url-redactor.d.ts +1 -0
  76. package/dist/utils/url-redactor.js +13 -0
  77. package/dist/utils/url-validator.js +8 -5
  78. package/dist/workers/transform-worker.js +82 -38
  79. package/package.json +8 -7
@@ -1,17 +1,17 @@
1
- import { randomUUID } from 'node:crypto';
2
1
  import { setInterval as setIntervalPromise } from 'node:timers/promises';
3
2
  import { config, enableHttpMode } from '../config/index.js';
4
- import { FetchError } from '../errors/app-error.js';
5
- import * as cache from '../services/cache.js';
6
- import { runWithRequestContext } from '../services/context.js';
7
3
  import { destroyAgents } from '../services/fetcher.js';
8
- import { logDebug, logError, logInfo, logWarn } from '../services/logger.js';
9
- import { parseCachedPayload, resolveCachedPayloadContent, } from '../utils/cached-payload.js';
4
+ import { logError, logInfo, logWarn } from '../services/logger.js';
5
+ import { shutdownTransformWorkerPool } from '../services/transform-worker-pool.js';
10
6
  import { getErrorMessage } from '../utils/error-details.js';
11
- import { generateSafeFilename } from '../utils/filename-generator.js';
12
7
  import { createAuthMetadataRouter, createAuthMiddleware } from './auth.js';
8
+ import { attachBaseMiddleware } from './base-middleware.js';
9
+ import { createCorsMiddleware } from './cors.js';
10
+ import { registerDownloadRoutes } from './download-routes.js';
11
+ import { errorHandler } from './error-handler.js';
13
12
  import { registerMcpRoutes } from './mcp-routes.js';
14
- import { createSessionStore, getSessionId, startSessionCleanupLoop, } from './mcp-sessions.js';
13
+ import { createSessionStore, startSessionCleanupLoop, } from './mcp-sessions.js';
14
+ import { applyHttpServerTuning, drainConnectionsOnShutdown, } from './server-tuning.js';
15
15
  function getRateLimitKey(req) {
16
16
  return req.ip ?? req.socket.remoteAddress ?? 'unknown';
17
17
  }
@@ -104,327 +104,6 @@ function handleRateLimitExceeded(res, entry, now, options) {
104
104
  });
105
105
  return true;
106
106
  }
107
- const LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1', '::1']);
108
- function getNonEmptyStringHeader(value) {
109
- if (typeof value !== 'string')
110
- return null;
111
- const trimmed = value.trim();
112
- return trimmed === '' ? null : trimmed;
113
- }
114
- function respondHostNotAllowed(res) {
115
- res.status(403).json({
116
- error: 'Host not allowed',
117
- code: 'HOST_NOT_ALLOWED',
118
- });
119
- }
120
- function respondOriginNotAllowed(res) {
121
- res.status(403).json({
122
- error: 'Origin not allowed',
123
- code: 'ORIGIN_NOT_ALLOWED',
124
- });
125
- }
126
- function tryParseOriginHostname(originHeader) {
127
- try {
128
- return new URL(originHeader).hostname.toLowerCase();
129
- }
130
- catch {
131
- return null;
132
- }
133
- }
134
- function takeFirstHostValue(value) {
135
- const first = value.split(',')[0];
136
- if (!first)
137
- return null;
138
- const trimmed = first.trim();
139
- return trimmed ? trimmed : null;
140
- }
141
- function stripIpv6Brackets(value) {
142
- if (!value.startsWith('['))
143
- return null;
144
- const end = value.indexOf(']');
145
- if (end === -1)
146
- return null;
147
- return value.slice(1, end);
148
- }
149
- function stripPortIfPresent(value) {
150
- const colonIndex = value.indexOf(':');
151
- if (colonIndex === -1)
152
- return value;
153
- return value.slice(0, colonIndex);
154
- }
155
- function normalizeHost(value) {
156
- const trimmed = value.trim().toLowerCase();
157
- if (!trimmed)
158
- return null;
159
- const first = takeFirstHostValue(trimmed);
160
- if (!first)
161
- return null;
162
- const ipv6 = stripIpv6Brackets(first);
163
- if (ipv6)
164
- return ipv6;
165
- return stripPortIfPresent(first);
166
- }
167
- function isWildcardHost(host) {
168
- return host === '0.0.0.0' || host === '::';
169
- }
170
- function addLoopbackHosts(allowedHosts) {
171
- for (const host of LOOPBACK_HOSTS) {
172
- allowedHosts.add(host);
173
- }
174
- }
175
- function addConfiguredHost(allowedHosts) {
176
- const configuredHost = normalizeHost(config.server.host);
177
- if (!configuredHost)
178
- return;
179
- if (isWildcardHost(configuredHost))
180
- return;
181
- allowedHosts.add(configuredHost);
182
- }
183
- function addExplicitAllowedHosts(allowedHosts) {
184
- for (const host of config.security.allowedHosts) {
185
- allowedHosts.add(host);
186
- }
187
- }
188
- function buildAllowedHosts() {
189
- const allowedHosts = new Set();
190
- addLoopbackHosts(allowedHosts);
191
- addConfiguredHost(allowedHosts);
192
- addExplicitAllowedHosts(allowedHosts);
193
- return allowedHosts;
194
- }
195
- function createHostValidationMiddleware() {
196
- const allowedHosts = buildAllowedHosts();
197
- return (req, res, next) => {
198
- const hostHeader = typeof req.headers.host === 'string' ? req.headers.host : '';
199
- const normalized = normalizeHost(hostHeader);
200
- if (!normalized || !allowedHosts.has(normalized)) {
201
- respondHostNotAllowed(res);
202
- return;
203
- }
204
- next();
205
- };
206
- }
207
- function createOriginValidationMiddleware() {
208
- const allowedHosts = buildAllowedHosts();
209
- return (req, res, next) => {
210
- const originHeader = getNonEmptyStringHeader(req.headers.origin);
211
- if (!originHeader) {
212
- next();
213
- return;
214
- }
215
- const originHostname = tryParseOriginHostname(originHeader);
216
- if (!originHostname || !allowedHosts.has(originHostname)) {
217
- respondOriginNotAllowed(res);
218
- return;
219
- }
220
- next();
221
- };
222
- }
223
- function createJsonParseErrorHandler() {
224
- return (err, _req, res, next) => {
225
- if (err instanceof SyntaxError && 'body' in err) {
226
- res.status(400).json({
227
- jsonrpc: '2.0',
228
- error: {
229
- code: -32700,
230
- message: 'Parse error: Invalid JSON',
231
- },
232
- id: null,
233
- });
234
- return;
235
- }
236
- next();
237
- };
238
- }
239
- function createContextMiddleware() {
240
- return (req, _res, next) => {
241
- const requestId = randomUUID();
242
- const sessionId = getSessionId(req);
243
- const context = sessionId === undefined ? { requestId } : { requestId, sessionId };
244
- runWithRequestContext(context, () => {
245
- next();
246
- });
247
- };
248
- }
249
- export function createCorsMiddleware() {
250
- return (req, res, next) => {
251
- // Handle OPTIONS preflight
252
- if (req.method === 'OPTIONS') {
253
- res.sendStatus(200);
254
- return;
255
- }
256
- next();
257
- };
258
- }
259
- function registerHealthRoute(app) {
260
- app.get('/health', (_req, res) => {
261
- res.json({
262
- status: 'healthy',
263
- name: config.server.name,
264
- version: config.server.version,
265
- uptime: process.uptime(),
266
- });
267
- });
268
- }
269
- export function attachBaseMiddleware(options) {
270
- const { app, jsonParser, rateLimitMiddleware, corsMiddleware } = options;
271
- app.use(createHostValidationMiddleware());
272
- app.use(createOriginValidationMiddleware());
273
- app.use(jsonParser);
274
- app.use(createContextMiddleware());
275
- app.use(createJsonParseErrorHandler());
276
- app.use(corsMiddleware);
277
- app.use('/mcp', rateLimitMiddleware);
278
- registerHealthRoute(app);
279
- }
280
- const HASH_PATTERN = /^[a-f0-9.]+$/i;
281
- function validateNamespace(namespace) {
282
- return namespace === 'markdown';
283
- }
284
- function validateHash(hash) {
285
- return HASH_PATTERN.test(hash) && hash.length >= 8 && hash.length <= 64;
286
- }
287
- function parseDownloadParams(req) {
288
- const { namespace, hash } = req.params;
289
- if (!namespace || !hash)
290
- return null;
291
- if (!validateNamespace(namespace))
292
- return null;
293
- if (!validateHash(hash))
294
- return null;
295
- return { namespace, hash };
296
- }
297
- function buildCacheKeyFromParams(params) {
298
- return `${params.namespace}:${params.hash}`;
299
- }
300
- function respondBadRequest(res, message) {
301
- res.status(400).json({
302
- error: message,
303
- code: 'BAD_REQUEST',
304
- });
305
- }
306
- function respondNotFound(res) {
307
- res.status(404).json({
308
- error: 'Content not found or expired',
309
- code: 'NOT_FOUND',
310
- });
311
- }
312
- function respondServiceUnavailable(res) {
313
- res.status(503).json({
314
- error: 'Download service is disabled',
315
- code: 'SERVICE_UNAVAILABLE',
316
- });
317
- }
318
- function resolveDownloadPayload(params, cacheEntry) {
319
- const payload = parseCachedPayload(cacheEntry.content);
320
- if (!payload)
321
- return null;
322
- const content = resolveCachedPayloadContent(payload);
323
- if (!content)
324
- return null;
325
- const safeTitle = typeof payload.title === 'string' ? payload.title : undefined;
326
- const fileName = generateSafeFilename(cacheEntry.url, cacheEntry.title ?? safeTitle, params.hash, '.md');
327
- return {
328
- content,
329
- contentType: 'text/markdown; charset=utf-8',
330
- fileName,
331
- };
332
- }
333
- function buildContentDisposition(fileName) {
334
- const encodedName = encodeURIComponent(fileName).replace(/'/g, '%27');
335
- return `attachment; filename="${fileName}"; filename*=UTF-8''${encodedName}`;
336
- }
337
- function sendDownloadPayload(res, payload) {
338
- const disposition = buildContentDisposition(payload.fileName);
339
- res.setHeader('Content-Type', payload.contentType);
340
- res.setHeader('Content-Disposition', disposition);
341
- res.setHeader('Cache-Control', `private, max-age=${config.cache.ttl}`);
342
- res.setHeader('X-Content-Type-Options', 'nosniff');
343
- res.send(payload.content);
344
- }
345
- function handleDownload(req, res) {
346
- if (!config.cache.enabled) {
347
- respondServiceUnavailable(res);
348
- return;
349
- }
350
- const params = parseDownloadParams(req);
351
- if (!params) {
352
- respondBadRequest(res, 'Invalid namespace or hash format');
353
- return;
354
- }
355
- const cacheKey = buildCacheKeyFromParams(params);
356
- const cacheEntry = cache.get(cacheKey);
357
- if (!cacheEntry) {
358
- logDebug('Download request for missing cache key', { cacheKey });
359
- respondNotFound(res);
360
- return;
361
- }
362
- const payload = resolveDownloadPayload(params, cacheEntry);
363
- if (!payload) {
364
- logDebug('Download payload unavailable', { cacheKey });
365
- respondNotFound(res);
366
- return;
367
- }
368
- logDebug('Serving download', { cacheKey, fileName: payload.fileName });
369
- sendDownloadPayload(res, payload);
370
- }
371
- export function registerDownloadRoutes(app) {
372
- app.get('/mcp/downloads/:namespace/:hash', handleDownload);
373
- }
374
- function getStatusCode(fetchError) {
375
- return fetchError ? fetchError.statusCode : 500;
376
- }
377
- function getErrorCode(fetchError) {
378
- return fetchError ? fetchError.code : 'INTERNAL_ERROR';
379
- }
380
- function getFetchErrorMessage(fetchError) {
381
- return fetchError ? fetchError.message : 'Internal Server Error';
382
- }
383
- function getErrorDetails(fetchError) {
384
- if (fetchError && Object.keys(fetchError.details).length > 0) {
385
- return fetchError.details;
386
- }
387
- return undefined;
388
- }
389
- function setRetryAfterHeader(res, fetchError) {
390
- const retryAfter = resolveRetryAfter(fetchError);
391
- if (retryAfter === undefined)
392
- return;
393
- res.set('Retry-After', retryAfter);
394
- }
395
- function buildErrorResponse(fetchError) {
396
- const details = getErrorDetails(fetchError);
397
- const response = {
398
- error: {
399
- message: getFetchErrorMessage(fetchError),
400
- code: getErrorCode(fetchError),
401
- statusCode: getStatusCode(fetchError),
402
- ...(details && { details }),
403
- },
404
- };
405
- // Never expose stack traces in production
406
- return response;
407
- }
408
- function resolveRetryAfter(fetchError) {
409
- if (fetchError?.statusCode !== 429)
410
- return undefined;
411
- const { retryAfter } = fetchError.details;
412
- return isRetryAfterValue(retryAfter) ? String(retryAfter) : undefined;
413
- }
414
- function isRetryAfterValue(value) {
415
- return typeof value === 'number' || typeof value === 'string';
416
- }
417
- export function errorHandler(err, req, res, next) {
418
- if (res.headersSent) {
419
- next(err);
420
- return;
421
- }
422
- const fetchError = err instanceof FetchError ? err : null;
423
- const statusCode = getStatusCode(fetchError);
424
- logError(`HTTP ${statusCode}: ${err.message} - ${req.method} ${req.path}`, err);
425
- setRetryAfterHeader(res, fetchError);
426
- res.status(statusCode).json(buildErrorResponse(fetchError));
427
- }
428
107
  function assertHttpConfiguration() {
429
108
  ensureBindAllowed();
430
109
  ensureStaticTokens();
@@ -438,7 +117,9 @@ function ensureBindAllowed() {
438
117
  logError('Refusing to bind to non-loopback host without ALLOW_REMOTE=true', { host: config.server.host });
439
118
  process.exit(1);
440
119
  }
441
- if (config.security.allowRemote && config.auth.mode !== 'oauth') {
120
+ if (!isLoopback &&
121
+ config.security.allowRemote &&
122
+ config.auth.mode !== 'oauth') {
442
123
  logError('Remote HTTP mode requires OAuth configuration; refusing to start');
443
124
  process.exit(1);
444
125
  }
@@ -464,7 +145,23 @@ function ensureOauthConfiguration() {
464
145
  }
465
146
  }
466
147
  function createShutdownHandler(server, sessionStore, sessionCleanupController, stopRateLimitCleanup) {
467
- return (signal) => shutdownServer(signal, server, sessionStore, sessionCleanupController, stopRateLimitCleanup);
148
+ let inFlight = null;
149
+ let initialSignal = null;
150
+ return (signal) => {
151
+ if (inFlight) {
152
+ logWarn('Shutdown already in progress; ignoring signal', {
153
+ signal,
154
+ initialSignal,
155
+ });
156
+ return inFlight;
157
+ }
158
+ initialSignal = signal;
159
+ inFlight = shutdownServer(signal, server, sessionStore, sessionCleanupController, stopRateLimitCleanup).catch((error) => {
160
+ logError('Shutdown handler failed', error instanceof Error ? error : { error: getErrorMessage(error) });
161
+ throw error;
162
+ });
163
+ return inFlight;
164
+ };
468
165
  }
469
166
  async function shutdownServer(signal, server, sessionStore, sessionCleanupController, stopRateLimitCleanup) {
470
167
  logInfo(`${signal} received, shutting down gracefully...`);
@@ -472,6 +169,8 @@ async function shutdownServer(signal, server, sessionStore, sessionCleanupContro
472
169
  sessionCleanupController.abort();
473
170
  await closeSessions(sessionStore);
474
171
  destroyAgents();
172
+ await shutdownTransformWorkerPool();
173
+ drainConnectionsOnShutdown(server);
475
174
  closeServer(server);
476
175
  scheduleForcedShutdown(10000);
477
176
  }
@@ -496,10 +195,10 @@ function scheduleForcedShutdown(timeoutMs) {
496
195
  }, timeoutMs).unref();
497
196
  }
498
197
  function registerSignalHandlers(shutdown) {
499
- process.on('SIGINT', () => {
198
+ process.once('SIGINT', () => {
500
199
  void shutdown('SIGINT');
501
200
  });
502
- process.on('SIGTERM', () => {
201
+ process.once('SIGTERM', () => {
503
202
  void shutdown('SIGTERM');
504
203
  });
505
204
  }
@@ -579,6 +278,7 @@ export async function startHttpServer() {
579
278
  enableHttpMode();
580
279
  const { app, sessionStore, sessionCleanupController, stopRateLimitCleanup } = await buildServerContext();
581
280
  const server = startListening(app);
281
+ applyHttpServerTuning(server);
582
282
  const shutdown = createShutdownHandler(server, sessionStore, sessionCleanupController, stopRateLimitCleanup);
583
283
  registerSignalHandlers(shutdown);
584
284
  return { shutdown };
package/dist/http.d.ts ADDED
@@ -0,0 +1,78 @@
1
+ import type { Express, NextFunction, Request, RequestHandler, Response } from 'express';
2
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
3
+ interface SessionEntry {
4
+ readonly transport: StreamableHTTPServerTransport;
5
+ createdAt: number;
6
+ lastSeen: number;
7
+ }
8
+ interface McpRequestParams {
9
+ _meta?: Record<string, unknown>;
10
+ [key: string]: unknown;
11
+ }
12
+ interface McpRequestBody {
13
+ jsonrpc: '2.0';
14
+ method: string;
15
+ id?: string | number;
16
+ params?: McpRequestParams;
17
+ }
18
+ export declare function startHttpServer(): Promise<{
19
+ shutdown: (signal: string) => Promise<void>;
20
+ }>;
21
+ export declare function errorHandler(err: Error, req: Request, res: Response, next: NextFunction): void;
22
+ export declare function normalizeHost(value: string): string | null;
23
+ export declare function attachBaseMiddleware(options: {
24
+ app: Express;
25
+ jsonParser: RequestHandler;
26
+ rateLimitMiddleware: RequestHandler;
27
+ corsMiddleware: RequestHandler;
28
+ }): void;
29
+ export declare function createCorsMiddleware(): (req: Request, res: Response, next: NextFunction) => void;
30
+ export interface SessionStore {
31
+ get: (sessionId: string) => SessionEntry | undefined;
32
+ touch: (sessionId: string) => void;
33
+ set: (sessionId: string, entry: SessionEntry) => void;
34
+ remove: (sessionId: string) => SessionEntry | undefined;
35
+ size: () => number;
36
+ clear: () => SessionEntry[];
37
+ evictExpired: () => SessionEntry[];
38
+ evictOldest: () => SessionEntry | undefined;
39
+ }
40
+ interface McpSessionOptions {
41
+ readonly sessionStore: SessionStore;
42
+ readonly maxSessions: number;
43
+ }
44
+ export declare function createSessionStore(sessionTtlMs: number): SessionStore;
45
+ export declare function reserveSessionSlot(store: SessionStore, maxSessions: number): boolean;
46
+ interface SlotTracker {
47
+ readonly releaseSlot: () => void;
48
+ readonly markInitialized: () => void;
49
+ readonly isInitialized: () => boolean;
50
+ }
51
+ export declare function createSlotTracker(): SlotTracker;
52
+ export declare function ensureSessionCapacity({ store, maxSessions, res, evictOldest, }: {
53
+ store: SessionStore;
54
+ maxSessions: number;
55
+ res: Response;
56
+ evictOldest: (store: SessionStore) => boolean;
57
+ }): boolean;
58
+ export declare function resolveTransportForPost({ res, body, sessionId, options, }: {
59
+ res: Response;
60
+ body: Pick<McpRequestBody, 'method' | 'id'>;
61
+ sessionId: string | undefined;
62
+ options: McpSessionOptions;
63
+ }): Promise<StreamableHTTPServerTransport | null>;
64
+ export declare function isJsonRpcBatchRequest(body: unknown): boolean;
65
+ export declare function isMcpRequestBody(body: unknown): body is McpRequestBody;
66
+ export declare function ensureMcpProtocolVersionHeader(req: Request, res: Response): boolean;
67
+ export declare function ensurePostAcceptHeader(req: Request): void;
68
+ export declare function acceptsEventStream(req: Request): boolean;
69
+ interface HttpServerTuningTarget {
70
+ headersTimeout?: number;
71
+ requestTimeout?: number;
72
+ keepAliveTimeout?: number;
73
+ closeIdleConnections?: () => void;
74
+ closeAllConnections?: () => void;
75
+ }
76
+ export declare function applyHttpServerTuning(server: HttpServerTuningTarget): void;
77
+ export declare function drainConnectionsOnShutdown(server: HttpServerTuningTarget): void;
78
+ export {};