@juspay/neurolink 12.20.0 → 12.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -6
- package/dist/browser/neurolink.min.js +386 -386
- package/dist/cli/commands/proxy.js +171 -25
- package/dist/cli/commands/telemetry.d.ts +4 -1
- package/dist/cli/commands/telemetry.js +113 -0
- package/dist/cli/utils/nativeTelemetry.d.ts +19 -0
- package/dist/cli/utils/nativeTelemetry.js +322 -0
- package/dist/proxy/bodyCapturePolicy.d.ts +6 -0
- package/dist/proxy/bodyCapturePolicy.js +132 -0
- package/dist/proxy/otelLogSink.d.ts +11 -1
- package/dist/proxy/otelLogSink.js +174 -27
- package/dist/proxy/otlpLogTransport.d.ts +4 -2
- package/dist/proxy/otlpLogTransport.js +22 -7
- package/dist/proxy/requestLogger.js +11 -3
- package/dist/proxy/restartControl.js +18 -3
- package/dist/proxy/rollingProxyServer.js +48 -1
- package/dist/proxy/rollingWorkerProcess.js +87 -10
- package/dist/proxy/rollingWorkerProtocol.js +4 -2
- package/dist/proxy/rollingWorkerSupervisor.js +47 -10
- package/dist/proxy/socketWorkerRuntime.js +90 -4
- package/dist/proxy/updateChecker.d.ts +3 -3
- package/dist/proxy/updateChecker.js +16 -9
- package/dist/proxy/updateCoordinator.d.ts +10 -1
- package/dist/proxy/updateCoordinator.js +68 -1
- package/dist/proxy/updateState.d.ts +2 -0
- package/dist/proxy/updateState.js +15 -0
- package/dist/types/cli.d.ts +18 -0
- package/dist/types/proxy.d.ts +97 -3
- package/docs-site/static/search-index.json +1 -1
- package/package.json +1 -1
- package/scripts/observability/NATIVE-DURABLE.md +68 -0
- package/scripts/observability/otel-collector.proxy-native-durable.yaml +2 -2
- package/scripts/observability/proxy-telemetry-check.mjs +135 -50
|
@@ -1,3 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse serving identity conservatively from `/status`. A rolling status is
|
|
3
|
+
* trusted only when the worker-local fields agree with the supervisor snapshot.
|
|
4
|
+
*/
|
|
5
|
+
export function parseProxyServingWorkerIdentity(payload, rollingSupervisor) {
|
|
6
|
+
if (!payload || typeof payload !== "object") {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
const value = payload;
|
|
10
|
+
if (typeof value.version !== "string" ||
|
|
11
|
+
!/^\d+\.\d+\.\d+$/.test(value.version) ||
|
|
12
|
+
!Number.isSafeInteger(value.pid) ||
|
|
13
|
+
Number(value.pid) <= 0) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
if (!rollingSupervisor) {
|
|
17
|
+
return { version: value.version, pid: Number(value.pid), generation: null };
|
|
18
|
+
}
|
|
19
|
+
const active = value.autoUpdate?.rolling?.active;
|
|
20
|
+
if (!active ||
|
|
21
|
+
active.version !== value.version ||
|
|
22
|
+
active.pid !== value.pid ||
|
|
23
|
+
!Number.isSafeInteger(active.generation) ||
|
|
24
|
+
Number(active.generation) <= 0) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
version: value.version,
|
|
29
|
+
pid: Number(value.pid),
|
|
30
|
+
generation: Number(active.generation),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/** A long-running update may mutate live selection only for its original worker. */
|
|
34
|
+
export function isSameProxyServingWorker(expected, current) {
|
|
35
|
+
return (current?.version === expected.version &&
|
|
36
|
+
current.pid === expected.pid &&
|
|
37
|
+
current.generation === expected.generation);
|
|
38
|
+
}
|
|
1
39
|
/**
|
|
2
40
|
* A replacement updater starts from the newly installed package. The registry
|
|
3
41
|
* then reports no newer version even when its launchd parent still runs the
|
|
@@ -72,6 +110,8 @@ function isQuiet(activity, quietThresholdMs, nowMs) {
|
|
|
72
110
|
}
|
|
73
111
|
/**
|
|
74
112
|
* Prefer a naturally quiet window, then briefly drain new inference traffic.
|
|
113
|
+
* Automatic supervisor refresh uses allowBusyDrain=false: it fences admission
|
|
114
|
+
* only after a quiet observation and never waits for busy workers behind it.
|
|
75
115
|
* Existing requests are never interrupted; a bounded drain failure is returned
|
|
76
116
|
* to the caller so admission can be reopened and retried later.
|
|
77
117
|
*/
|
|
@@ -89,16 +129,43 @@ export async function waitForProxyUpdateWindow(options) {
|
|
|
89
129
|
}
|
|
90
130
|
const activity = await options.getActivity();
|
|
91
131
|
if (activity && isQuiet(activity, options.quietThresholdMs, now())) {
|
|
92
|
-
|
|
132
|
+
if (options.allowBusyDrain !== false) {
|
|
133
|
+
return { ready: true, draining: false };
|
|
134
|
+
}
|
|
135
|
+
break;
|
|
93
136
|
}
|
|
94
137
|
options.onPhase?.("waiting_for_quiet", activity);
|
|
95
138
|
await wait(options.pollIntervalMs);
|
|
96
139
|
}
|
|
140
|
+
if (options.allowBusyDrain === false) {
|
|
141
|
+
if (options.isStopping()) {
|
|
142
|
+
return { ready: false, draining: false, reason: "stopping" };
|
|
143
|
+
}
|
|
144
|
+
if (!options.isParentAlive()) {
|
|
145
|
+
return { ready: false, draining: false, reason: "parent_stopped" };
|
|
146
|
+
}
|
|
147
|
+
const activity = await options.getActivity();
|
|
148
|
+
if (!activity || !isQuiet(activity, options.quietThresholdMs, now())) {
|
|
149
|
+
options.onPhase?.("waiting_for_quiet", activity);
|
|
150
|
+
return { ready: false, draining: false, reason: "busy" };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
97
153
|
if (!(await options.setDraining(true))) {
|
|
98
154
|
// The control response may have been lost after the parent applied it.
|
|
99
155
|
// Treat state as unknown/draining so the caller always attempts resume.
|
|
100
156
|
return { ready: false, draining: true, reason: "drain_failed" };
|
|
101
157
|
}
|
|
158
|
+
if (options.allowBusyDrain === false) {
|
|
159
|
+
// The worker's idle fence closes the observation/admission race. Any new
|
|
160
|
+
// cross-generation activity means resume immediately, never hold a busy
|
|
161
|
+
// service behind maintenance responses for the normal drain timeout.
|
|
162
|
+
const activity = await options.getActivity();
|
|
163
|
+
if (activity && isProxyRuntimeSettled(activity)) {
|
|
164
|
+
return { ready: true, draining: true };
|
|
165
|
+
}
|
|
166
|
+
const resumed = await options.setDraining(false);
|
|
167
|
+
return { ready: false, draining: !resumed, reason: "busy" };
|
|
168
|
+
}
|
|
102
169
|
const drainDeadline = now() + options.drainWaitMs;
|
|
103
170
|
while (now() < drainDeadline) {
|
|
104
171
|
if (options.isStopping()) {
|
|
@@ -67,3 +67,5 @@ export declare function reconcileRunningUpdate(runningVersion: string, stateFile
|
|
|
67
67
|
* @param stateFilePath - Override path for testing
|
|
68
68
|
*/
|
|
69
69
|
export declare function recordCheck(latestVersion: string, stateFilePath?: string): void;
|
|
70
|
+
/** Preserve the last successful registry version when discovery fails. */
|
|
71
|
+
export declare function recordCheckFailure(message: string, stateFilePath?: string): void;
|
|
@@ -124,6 +124,12 @@ export function loadUpdateState(stateFilePath) {
|
|
|
124
124
|
return {
|
|
125
125
|
...getDefaultUpdateState(),
|
|
126
126
|
...candidate,
|
|
127
|
+
lastCheckAttemptAt: typeof candidate.lastCheckAttemptAt === "string"
|
|
128
|
+
? candidate.lastCheckAttemptAt
|
|
129
|
+
: undefined,
|
|
130
|
+
lastCheckError: typeof candidate.lastCheckError === "string"
|
|
131
|
+
? candidate.lastCheckError.slice(0, 1_000)
|
|
132
|
+
: null,
|
|
127
133
|
suppressedVersions: candidate.suppressedVersions ?? {},
|
|
128
134
|
// Backfill order matters for state files written before `installedVersion`
|
|
129
135
|
// existed. Back then `recordUpdateInstalled()` set ONLY
|
|
@@ -303,6 +309,15 @@ export function reconcileRunningUpdate(runningVersion, stateFilePath) {
|
|
|
303
309
|
export function recordCheck(latestVersion, stateFilePath) {
|
|
304
310
|
const state = loadUpdateState(stateFilePath) ?? getDefaultUpdateState();
|
|
305
311
|
state.lastCheckAt = new Date().toISOString();
|
|
312
|
+
state.lastCheckAttemptAt = state.lastCheckAt;
|
|
313
|
+
state.lastCheckError = null;
|
|
306
314
|
state.lastCheckVersion = latestVersion;
|
|
307
315
|
saveUpdateState(state, stateFilePath);
|
|
308
316
|
}
|
|
317
|
+
/** Preserve the last successful registry version when discovery fails. */
|
|
318
|
+
export function recordCheckFailure(message, stateFilePath) {
|
|
319
|
+
const state = loadUpdateState(stateFilePath) ?? getDefaultUpdateState();
|
|
320
|
+
state.lastCheckAttemptAt = new Date().toISOString();
|
|
321
|
+
state.lastCheckError = message.slice(0, 1_000);
|
|
322
|
+
saveUpdateState(state, stateFilePath);
|
|
323
|
+
}
|
package/dist/types/cli.d.ts
CHANGED
|
@@ -1044,6 +1044,24 @@ export type TelemetryStatsArgs = TelemetryCommandArgs & {
|
|
|
1044
1044
|
byModel?: boolean;
|
|
1045
1045
|
byProvider?: boolean;
|
|
1046
1046
|
};
|
|
1047
|
+
/** Staging-only native collector migration; never activates a service. */
|
|
1048
|
+
export type TelemetryNativePrepareArgs = {
|
|
1049
|
+
collectorConfig: string;
|
|
1050
|
+
output: string;
|
|
1051
|
+
queueDirectory: string;
|
|
1052
|
+
compactionDirectory: string;
|
|
1053
|
+
bodyPort: number;
|
|
1054
|
+
bodyStream?: string;
|
|
1055
|
+
metadataQueueMib: number;
|
|
1056
|
+
bodyQueueMib: number;
|
|
1057
|
+
diskQuotaMib: number;
|
|
1058
|
+
metadataRetentionDays: number;
|
|
1059
|
+
bodyRetentionDays: number;
|
|
1060
|
+
};
|
|
1061
|
+
export type TelemetryNativeValidateArgs = {
|
|
1062
|
+
directory: string;
|
|
1063
|
+
collectorBin: string;
|
|
1064
|
+
};
|
|
1047
1065
|
/** Available exporter names */
|
|
1048
1066
|
export type ExporterName = "langfuse" | "langsmith" | "otel" | "datadog" | "sentry" | "braintrust" | "arize" | "posthog" | "laminar";
|
|
1049
1067
|
/** Observability command arguments */
|
package/dist/types/proxy.d.ts
CHANGED
|
@@ -16,6 +16,11 @@ import type { ProxyContextEvidence, ProxyPreparedContext } from "./proxyContext.
|
|
|
16
16
|
* - src/lib/server/routes/claudeProxyRoutes.ts (runtime state, deps)
|
|
17
17
|
*/
|
|
18
18
|
import type { Counter, Histogram, Span } from "@opentelemetry/api";
|
|
19
|
+
import type { LogRecordExporter } from "@opentelemetry/sdk-logs";
|
|
20
|
+
/** HTTP exporter with an explicit bounded-shutdown cancellation boundary. */
|
|
21
|
+
export type ProxyOtlpLogTransport = LogRecordExporter & {
|
|
22
|
+
abortPending: () => void;
|
|
23
|
+
};
|
|
19
24
|
import type { Hono } from "hono";
|
|
20
25
|
import type { Ora } from "ora";
|
|
21
26
|
import type { MCPToolRegistry } from "../mcp/toolRegistry.js";
|
|
@@ -726,6 +731,49 @@ export type ProxyBodyDeliveryResult = {
|
|
|
726
731
|
maxChunkExportMs?: number;
|
|
727
732
|
reason?: string;
|
|
728
733
|
};
|
|
734
|
+
/** One-hop reference to identical redacted bytes from the same request. */
|
|
735
|
+
export type ProxyBodyCaptureReference = {
|
|
736
|
+
captureId: string;
|
|
737
|
+
requestId: string;
|
|
738
|
+
bodySha256: string;
|
|
739
|
+
redactedBodyBytes: number;
|
|
740
|
+
/** Source export settlement time, used to bound backend reconstruction. */
|
|
741
|
+
exportedAt: string;
|
|
742
|
+
};
|
|
743
|
+
export type ProxyBodyCapturePublication = {
|
|
744
|
+
delivery?: ProxyBodyDeliveryResult | {
|
|
745
|
+
status: "reference";
|
|
746
|
+
reason: "identical_redacted_payload";
|
|
747
|
+
} | {
|
|
748
|
+
status: "policy_excluded";
|
|
749
|
+
reason: "body_byte_budget_exhausted" | "body_byte_budget_invalid";
|
|
750
|
+
};
|
|
751
|
+
reference?: ProxyBodyCaptureReference;
|
|
752
|
+
};
|
|
753
|
+
export type ProxyBodyCapturePolicyOptions = {
|
|
754
|
+
bytesPerMinute?: string;
|
|
755
|
+
deduplicate?: boolean;
|
|
756
|
+
now?: () => number;
|
|
757
|
+
};
|
|
758
|
+
export type ProxyBodyCapturePolicySnapshot = {
|
|
759
|
+
deduplicationEnabled: boolean;
|
|
760
|
+
maxReferences: number;
|
|
761
|
+
referenceTtlMs: number;
|
|
762
|
+
references: number;
|
|
763
|
+
byteBudgetPerMinute: number | null;
|
|
764
|
+
invalidByteBudget: boolean;
|
|
765
|
+
availableBytes: number | null;
|
|
766
|
+
submittedBytes: number;
|
|
767
|
+
deduplicatedCaptures: number;
|
|
768
|
+
deduplicatedBytes: number;
|
|
769
|
+
policyExcludedCaptures: number;
|
|
770
|
+
policyExcludedBytes: number;
|
|
771
|
+
};
|
|
772
|
+
export type ProxyBodyDeduplicationEntry = {
|
|
773
|
+
reference: ProxyBodyCaptureReference;
|
|
774
|
+
expiresAt: number;
|
|
775
|
+
acknowledged: Promise<boolean>;
|
|
776
|
+
};
|
|
729
777
|
/** One bounded body publication, tracked across exporter callbacks. */
|
|
730
778
|
export type ProxyBodyPublicationProgress = {
|
|
731
779
|
acknowledged: number;
|
|
@@ -828,6 +876,7 @@ export type ProxyRequestLoggerSnapshot = {
|
|
|
828
876
|
diskEnabled?: boolean;
|
|
829
877
|
otel?: ReturnType<typeof import("../proxy/otelLogSink.js").getProxyOtelLogSnapshot>;
|
|
830
878
|
bodyCapture?: ProxyBodyCaptureWorkerSnapshot;
|
|
879
|
+
bodyCapturePolicy?: ProxyBodyCapturePolicySnapshot;
|
|
831
880
|
enabled: boolean;
|
|
832
881
|
requests: ProxyRequestLogSinkSnapshot;
|
|
833
882
|
attempts: ProxyRequestLogSinkSnapshot;
|
|
@@ -2563,6 +2612,20 @@ export type UpdateCheckResult = {
|
|
|
2563
2612
|
currentVersion: string;
|
|
2564
2613
|
latestVersion: string;
|
|
2565
2614
|
updateAvailable: boolean;
|
|
2615
|
+
} & ({
|
|
2616
|
+
checkSucceeded: true;
|
|
2617
|
+
checkError?: never;
|
|
2618
|
+
} | {
|
|
2619
|
+
checkSucceeded: false;
|
|
2620
|
+
checkError: string;
|
|
2621
|
+
updateAvailable: false;
|
|
2622
|
+
});
|
|
2623
|
+
/** Immutable identity of the worker an update attempt is allowed to replace. */
|
|
2624
|
+
export type ProxyServingWorkerIdentity = {
|
|
2625
|
+
version: string;
|
|
2626
|
+
pid: number;
|
|
2627
|
+
/** Rolling generation; null only for a legacy single-process service. */
|
|
2628
|
+
generation: number | null;
|
|
2566
2629
|
};
|
|
2567
2630
|
/** Result of one local proxy health probe by the updater or fail-open guard. */
|
|
2568
2631
|
export type ProxyHealthProbe = {
|
|
@@ -2587,6 +2650,9 @@ export type SuppressedVersion = {
|
|
|
2587
2650
|
export type UpdateState = {
|
|
2588
2651
|
lastCheckAt: string;
|
|
2589
2652
|
lastCheckVersion: string;
|
|
2653
|
+
/** Last attempt is distinct from the last successful registry observation. */
|
|
2654
|
+
lastCheckAttemptAt?: string;
|
|
2655
|
+
lastCheckError?: string | null;
|
|
2590
2656
|
suppressedVersions: Record<string, SuppressedVersion>;
|
|
2591
2657
|
/**
|
|
2592
2658
|
* Last package version whose stable trampoline was successfully validated.
|
|
@@ -2621,7 +2687,7 @@ export type UpdateState = {
|
|
|
2621
2687
|
export type ProxyUpdateWindowResult = {
|
|
2622
2688
|
ready: boolean;
|
|
2623
2689
|
draining: boolean;
|
|
2624
|
-
reason?: "stopping" | "parent_stopped" | "drain_failed" | "drain_timeout";
|
|
2690
|
+
reason?: "stopping" | "parent_stopped" | "drain_failed" | "drain_timeout" | "busy";
|
|
2625
2691
|
};
|
|
2626
2692
|
/** Dependencies and timing controls for the updater's safe-window coordinator. */
|
|
2627
2693
|
export type ProxyUpdateWindowOptions = {
|
|
@@ -2629,6 +2695,8 @@ export type ProxyUpdateWindowOptions = {
|
|
|
2629
2695
|
quietWaitMs: number;
|
|
2630
2696
|
drainWaitMs: number;
|
|
2631
2697
|
pollIntervalMs: number;
|
|
2698
|
+
/** Automatic supervisor refresh must defer instead of draining busy traffic. */
|
|
2699
|
+
allowBusyDrain?: boolean;
|
|
2632
2700
|
getActivity: () => Promise<ProxyRuntimeActivity | null>;
|
|
2633
2701
|
setDraining: (draining: boolean) => Promise<boolean>;
|
|
2634
2702
|
isStopping: () => boolean;
|
|
@@ -2705,6 +2773,10 @@ export type ProxyWorkerControlMessage = {
|
|
|
2705
2773
|
} | {
|
|
2706
2774
|
type: "proxy-worker:shutdown";
|
|
2707
2775
|
generation: number;
|
|
2776
|
+
} | {
|
|
2777
|
+
type: "proxy-worker:socket-offer";
|
|
2778
|
+
generation: number;
|
|
2779
|
+
socketId: string;
|
|
2708
2780
|
} | {
|
|
2709
2781
|
type: "proxy-worker:socket-commit";
|
|
2710
2782
|
generation: number;
|
|
@@ -2720,6 +2792,10 @@ export type ProxyWorkerStatusMessage = {
|
|
|
2720
2792
|
pid: number;
|
|
2721
2793
|
version: string;
|
|
2722
2794
|
processInstanceId?: string;
|
|
2795
|
+
/** New supervisors defer sending descriptors until commit. */
|
|
2796
|
+
socketOfferProtocol?: "control-before-handle";
|
|
2797
|
+
/** New workers confirm descriptor adoption before a generation drains. */
|
|
2798
|
+
socketCommitProtocol?: "worker-ack";
|
|
2723
2799
|
} | {
|
|
2724
2800
|
type: "proxy-worker:drained";
|
|
2725
2801
|
generation: number;
|
|
@@ -2738,6 +2814,11 @@ export type ProxyWorkerStatusMessage = {
|
|
|
2738
2814
|
generation: number;
|
|
2739
2815
|
pid: number;
|
|
2740
2816
|
socketId: string;
|
|
2817
|
+
} | {
|
|
2818
|
+
type: "proxy-worker:socket-committed";
|
|
2819
|
+
generation: number;
|
|
2820
|
+
pid: number;
|
|
2821
|
+
socketId: string;
|
|
2741
2822
|
} | {
|
|
2742
2823
|
type: "proxy-worker:replacement-requested";
|
|
2743
2824
|
generation: number;
|
|
@@ -2755,8 +2836,12 @@ export type TransferableProxySocket = Pick<import("node:net").Socket, "destroy"
|
|
|
2755
2836
|
export type DetachableTransferableProxySocket = TransferableProxySocket & Pick<import("node:net").Socket, "off">;
|
|
2756
2837
|
export type RollingWorkerHandle = {
|
|
2757
2838
|
pid: number;
|
|
2839
|
+
/** Combined offer and commit budget, before any retry. Defaults to 60s. */
|
|
2840
|
+
socketTransferTimeoutMs?: number;
|
|
2758
2841
|
sendControl: (message: ProxyWorkerControlMessage) => void;
|
|
2759
|
-
sendSocket: (generation: number, socket: TransferableProxySocket, callback: (error?: Error | null) => void
|
|
2842
|
+
sendSocket: (generation: number, socket: TransferableProxySocket, callback: (error?: Error | null) => void,
|
|
2843
|
+
/** Absolute admission deadline shared by queueing and any safe retry. */
|
|
2844
|
+
deadlineAt?: number) => void;
|
|
2760
2845
|
terminate: (signal?: NodeJS.Signals) => void;
|
|
2761
2846
|
onMessage: (listener: (message: ProxyWorkerStatusMessage) => void) => () => void;
|
|
2762
2847
|
onExit: (listener: (code: number | null, signal: string | null) => void) => () => void;
|
|
@@ -2853,6 +2938,9 @@ export type RollingProxyServerOptions = {
|
|
|
2853
2938
|
shutdownTimeoutMs?: number;
|
|
2854
2939
|
recoveryDelayMs?: number;
|
|
2855
2940
|
maxRecoveryDelayMs?: number;
|
|
2941
|
+
/** Backoff after failed pressure-induced replacement, while old worker serves. */
|
|
2942
|
+
stallReplacementDelayMs?: number;
|
|
2943
|
+
maxStallReplacementDelayMs?: number;
|
|
2856
2944
|
onStateChange?: (snapshot: RollingWorkerSupervisorSnapshot) => void;
|
|
2857
2945
|
onEvent?: (event: RollingWorkerSupervisorEvent) => void;
|
|
2858
2946
|
log?: (message: string) => void;
|
|
@@ -2879,8 +2967,12 @@ export type RollingCandidateWorker = RollingManagedWorker & {
|
|
|
2879
2967
|
activationRequested: boolean;
|
|
2880
2968
|
settle: (error?: Error) => void;
|
|
2881
2969
|
};
|
|
2882
|
-
export type
|
|
2970
|
+
export type RollingSocketAdmission = {
|
|
2883
2971
|
socket: TransferableProxySocket;
|
|
2972
|
+
deadlineAt: number;
|
|
2973
|
+
retried: boolean;
|
|
2974
|
+
};
|
|
2975
|
+
export type RollingQueuedSocket = RollingSocketAdmission & {
|
|
2884
2976
|
timeout: NodeJS.Timeout;
|
|
2885
2977
|
};
|
|
2886
2978
|
export type SocketWorkerRuntime = {
|
|
@@ -2896,6 +2988,8 @@ export type SocketWorkerRuntime = {
|
|
|
2896
2988
|
};
|
|
2897
2989
|
export type SocketWorkerRuntimeOptions = {
|
|
2898
2990
|
onDrained?: () => void;
|
|
2991
|
+
/** Bound for a committed socket's first HTTP request while draining. */
|
|
2992
|
+
firstRequestGraceMs?: number;
|
|
2899
2993
|
};
|
|
2900
2994
|
/** Shape of the dynamically-imported js-yaml module. `dump` is optional —
|
|
2901
2995
|
* read-only consumers (proxy config loader) only need `load`; writers
|