stream-guard 1.0.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.
@@ -0,0 +1,270 @@
1
+ import { Readable, Writable, Duplex, Transform } from 'node:stream';
2
+ import { EventEmitter } from 'node:events';
3
+
4
+ /**
5
+ * Base error class for all stream-guard errors.
6
+ * Provides consistent error structure and metadata.
7
+ */
8
+ declare abstract class StreamGuardError extends Error {
9
+ /** Timestamp when the error occurred */
10
+ readonly timestamp: number;
11
+ /** The constraint that was violated */
12
+ abstract readonly constraint: string;
13
+ constructor(message: string);
14
+ }
15
+ /**
16
+ * Thrown when a stream exceeds the absolute timeout limit.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * stream.on('error', (err) => {
21
+ * if (err instanceof StreamTimeoutError) {
22
+ * console.log(`Stream timed out after ${err.timeoutMs}ms`);
23
+ * }
24
+ * });
25
+ * ```
26
+ */
27
+ declare class StreamTimeoutError extends StreamGuardError {
28
+ readonly constraint = "timeout";
29
+ /** The timeout limit that was exceeded (in milliseconds) */
30
+ readonly timeoutMs: number;
31
+ /** Actual elapsed time when the timeout was triggered */
32
+ readonly elapsedMs: number;
33
+ constructor(timeoutMs: number, elapsedMs: number);
34
+ }
35
+ /**
36
+ * Thrown when a stream stalls (no data flow) for too long.
37
+ * This typically indicates a zombie connection or slow loris attack.
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * stream.on('error', (err) => {
42
+ * if (err instanceof StreamStalledError) {
43
+ * console.log(`Stream stalled for ${err.stalledMs}ms`);
44
+ * }
45
+ * });
46
+ * ```
47
+ */
48
+ declare class StreamStalledError extends StreamGuardError {
49
+ readonly constraint = "stalled";
50
+ /** The stall threshold that was exceeded (in milliseconds) */
51
+ readonly stalledMs: number;
52
+ /** Time since last data activity */
53
+ readonly idleMs: number;
54
+ constructor(stalledMs: number, idleMs: number);
55
+ }
56
+ /**
57
+ * Thrown when a stream exceeds the maximum allowed bytes.
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * stream.on('error', (err) => {
62
+ * if (err instanceof StreamLimitError) {
63
+ * console.log(`Stream exceeded ${err.maxBytes} bytes`);
64
+ * }
65
+ * });
66
+ * ```
67
+ */
68
+ declare class StreamLimitError extends StreamGuardError {
69
+ readonly constraint = "maxBytes";
70
+ /** The maximum bytes limit that was exceeded */
71
+ readonly maxBytes: number;
72
+ /** The actual number of bytes transferred */
73
+ readonly bytesTransferred: number;
74
+ constructor(maxBytes: number, bytesTransferred: number);
75
+ }
76
+ /**
77
+ * Thrown when the process heap usage exceeds the configured limit during stream processing.
78
+ * This is a safety mechanism to prevent OOM crashes.
79
+ *
80
+ * @example
81
+ * ```ts
82
+ * stream.on('error', (err) => {
83
+ * if (err instanceof StreamHeapError) {
84
+ * console.log(`Heap exceeded: ${err.heapUsed} / ${err.maxHeap}`);
85
+ * }
86
+ * });
87
+ * ```
88
+ */
89
+ declare class StreamHeapError extends StreamGuardError {
90
+ readonly constraint = "maxHeap";
91
+ /** The maximum heap limit that was exceeded (in bytes) */
92
+ readonly maxHeap: number;
93
+ /** The actual heap usage when the limit was exceeded (in bytes) */
94
+ readonly heapUsed: number;
95
+ constructor(maxHeap: number, heapUsed: number);
96
+ }
97
+ /**
98
+ * Type guard to check if an error is a StreamGuardError.
99
+ */
100
+ declare function isStreamGuardError(error: unknown): error is StreamGuardError;
101
+ /**
102
+ * Type guard to check if an error is a StreamTimeoutError.
103
+ */
104
+ declare function isStreamTimeoutError(error: unknown): error is StreamTimeoutError;
105
+ /**
106
+ * Type guard to check if an error is a StreamStalledError.
107
+ */
108
+ declare function isStreamStalledError(error: unknown): error is StreamStalledError;
109
+ /**
110
+ * Type guard to check if an error is a StreamLimitError.
111
+ */
112
+ declare function isStreamLimitError(error: unknown): error is StreamLimitError;
113
+ /**
114
+ * Type guard to check if an error is a StreamHeapError.
115
+ */
116
+ declare function isStreamHeapError(error: unknown): error is StreamHeapError;
117
+
118
+ /**
119
+ * Supported stream types that can be guarded.
120
+ */
121
+ type GuardableStream = (Readable | Writable | Duplex | Transform) & EventEmitter;
122
+ /**
123
+ * Configuration options for the stream guard.
124
+ */
125
+ interface GuardOptions {
126
+ /**
127
+ * Absolute timeout in milliseconds.
128
+ * If the stream doesn't complete within this time, it will be destroyed.
129
+ *
130
+ * @example
131
+ * ```ts
132
+ * guard(stream, { timeout: 30000 }); // 30 second timeout
133
+ * ```
134
+ */
135
+ timeout?: number;
136
+ /**
137
+ * Stall protection timeout in milliseconds.
138
+ * If no data flows for this duration, the stream will be destroyed.
139
+ * Useful for detecting zombie connections and slow loris attacks.
140
+ *
141
+ * @example
142
+ * ```ts
143
+ * guard(stream, { stalled: 5000 }); // Kill if no data for 5 seconds
144
+ * ```
145
+ */
146
+ stalled?: number;
147
+ /**
148
+ * Maximum bytes allowed through the stream.
149
+ * If exceeded, the stream will be destroyed.
150
+ *
151
+ * @example
152
+ * ```ts
153
+ * guard(stream, { maxBytes: 10 * 1024 * 1024 }); // 10MB limit
154
+ * ```
155
+ */
156
+ maxBytes?: number;
157
+ /**
158
+ * Maximum heap memory usage in bytes.
159
+ * If process.memoryUsage().heapUsed exceeds this during stream processing,
160
+ * the stream will be destroyed to prevent OOM crashes.
161
+ *
162
+ * @example
163
+ * ```ts
164
+ * guard(stream, { maxHeap: 500 * 1024 * 1024 }); // 500MB heap limit
165
+ * ```
166
+ */
167
+ maxHeap?: number;
168
+ /**
169
+ * Interval in milliseconds for heap memory checks.
170
+ * Lower values provide faster detection but use more CPU.
171
+ *
172
+ * @default 100
173
+ */
174
+ heapCheckInterval?: number;
175
+ /**
176
+ * Number of chunks between heap checks.
177
+ * Alternative to time-based heap checking.
178
+ * If both are set, checks happen on whichever triggers first.
179
+ *
180
+ * @default undefined (time-based only)
181
+ */
182
+ heapCheckChunks?: number;
183
+ /**
184
+ * Called when the stream is destroyed by the guard.
185
+ * Useful for logging and monitoring.
186
+ */
187
+ onDestroy?: (error: Error) => void;
188
+ }
189
+ /**
190
+ * Metadata attached to guarded streams.
191
+ */
192
+ interface GuardMetadata {
193
+ /** Unique guard instance ID */
194
+ readonly guardId: string;
195
+ /** When the guard was attached */
196
+ readonly startTime: number;
197
+ /** Get current bytes transferred */
198
+ getBytesTransferred(): number;
199
+ /** Get elapsed time in milliseconds */
200
+ getElapsedTime(): number;
201
+ /** Check if guard is still active */
202
+ isActive(): boolean;
203
+ /** Manually release all guards and timers */
204
+ release(): void;
205
+ }
206
+ /**
207
+ * Check if a stream has already been guarded.
208
+ */
209
+ declare function isGuarded(stream: GuardableStream): boolean;
210
+ /**
211
+ * Get the guard metadata from a guarded stream.
212
+ * Returns undefined if the stream is not guarded.
213
+ */
214
+ declare function getGuardMetadata(stream: GuardableStream): GuardMetadata | undefined;
215
+ /**
216
+ * Guards a Node.js stream with safety constraints.
217
+ *
218
+ * Wraps any Readable, Writable, Duplex, or Transform stream and enforces:
219
+ * - Absolute timeout (total time to complete)
220
+ * - Stall detection (idle time without data flow)
221
+ * - Byte limits (maximum data throughput)
222
+ * - Heap memory limits (prevent OOM)
223
+ *
224
+ * @param stream - The stream to guard
225
+ * @param options - Guard configuration options
226
+ * @returns The same stream with guards attached
227
+ *
228
+ * @example
229
+ * ```ts
230
+ * import { guard, StreamTimeoutError } from 'stream-guard';
231
+ * import { createReadStream } from 'node:fs';
232
+ *
233
+ * const stream = guard(createReadStream('file.txt'), {
234
+ * timeout: 30000, // 30s absolute timeout
235
+ * stalled: 5000, // 5s stall detection
236
+ * maxBytes: 10 * 1024 * 1024, // 10MB limit
237
+ * });
238
+ *
239
+ * stream.on('error', (err) => {
240
+ * if (err instanceof StreamTimeoutError) {
241
+ * console.log('Stream timed out!');
242
+ * }
243
+ * });
244
+ *
245
+ * stream.pipe(destination);
246
+ * ```
247
+ */
248
+ declare function guard<T extends GuardableStream>(stream: T, options?: GuardOptions): T;
249
+ /**
250
+ * Creates a pre-configured guard factory with default options.
251
+ * Useful for applying consistent settings across multiple streams.
252
+ *
253
+ * @param defaultOptions - Default options for all guards created by this factory
254
+ * @returns A guard function with defaults pre-applied
255
+ *
256
+ * @example
257
+ * ```ts
258
+ * const apiGuard = createGuard({
259
+ * timeout: 30000,
260
+ * maxBytes: 5 * 1024 * 1024,
261
+ * });
262
+ *
263
+ * // All streams will have these defaults
264
+ * apiGuard(stream1);
265
+ * apiGuard(stream2, { stalled: 5000 }); // Can override
266
+ * ```
267
+ */
268
+ declare function createGuard(defaultOptions: GuardOptions): <T extends GuardableStream>(stream: T, options?: GuardOptions) => T;
269
+
270
+ export { type GuardMetadata, type GuardOptions, type GuardableStream, StreamGuardError, StreamHeapError, StreamLimitError, StreamStalledError, StreamTimeoutError, createGuard, guard as default, getGuardMetadata, guard, isGuarded, isStreamGuardError, isStreamHeapError, isStreamLimitError, isStreamStalledError, isStreamTimeoutError };
package/dist/index.js ADDED
@@ -0,0 +1,316 @@
1
+ // src/errors.ts
2
+ var StreamGuardError = class extends Error {
3
+ /** Timestamp when the error occurred */
4
+ timestamp;
5
+ constructor(message) {
6
+ super(message);
7
+ this.name = this.constructor.name;
8
+ this.timestamp = Date.now();
9
+ if (Error.captureStackTrace) {
10
+ Error.captureStackTrace(this, this.constructor);
11
+ }
12
+ }
13
+ };
14
+ var StreamTimeoutError = class extends StreamGuardError {
15
+ constraint = "timeout";
16
+ /** The timeout limit that was exceeded (in milliseconds) */
17
+ timeoutMs;
18
+ /** Actual elapsed time when the timeout was triggered */
19
+ elapsedMs;
20
+ constructor(timeoutMs, elapsedMs) {
21
+ super(`Stream exceeded absolute timeout of ${timeoutMs}ms (elapsed: ${elapsedMs}ms)`);
22
+ this.timeoutMs = timeoutMs;
23
+ this.elapsedMs = elapsedMs;
24
+ }
25
+ };
26
+ var StreamStalledError = class extends StreamGuardError {
27
+ constraint = "stalled";
28
+ /** The stall threshold that was exceeded (in milliseconds) */
29
+ stalledMs;
30
+ /** Time since last data activity */
31
+ idleMs;
32
+ constructor(stalledMs, idleMs) {
33
+ super(`Stream stalled: no data flow for ${idleMs}ms (limit: ${stalledMs}ms)`);
34
+ this.stalledMs = stalledMs;
35
+ this.idleMs = idleMs;
36
+ }
37
+ };
38
+ var StreamLimitError = class extends StreamGuardError {
39
+ constraint = "maxBytes";
40
+ /** The maximum bytes limit that was exceeded */
41
+ maxBytes;
42
+ /** The actual number of bytes transferred */
43
+ bytesTransferred;
44
+ constructor(maxBytes, bytesTransferred) {
45
+ super(`Stream exceeded byte limit: ${bytesTransferred} bytes transferred (limit: ${maxBytes} bytes)`);
46
+ this.maxBytes = maxBytes;
47
+ this.bytesTransferred = bytesTransferred;
48
+ }
49
+ };
50
+ var StreamHeapError = class extends StreamGuardError {
51
+ constraint = "maxHeap";
52
+ /** The maximum heap limit that was exceeded (in bytes) */
53
+ maxHeap;
54
+ /** The actual heap usage when the limit was exceeded (in bytes) */
55
+ heapUsed;
56
+ constructor(maxHeap, heapUsed) {
57
+ super(`Process heap exceeded limit during stream: ${formatBytes(heapUsed)} used (limit: ${formatBytes(maxHeap)})`);
58
+ this.maxHeap = maxHeap;
59
+ this.heapUsed = heapUsed;
60
+ }
61
+ };
62
+ function formatBytes(bytes) {
63
+ if (bytes < 1024) return `${bytes}B`;
64
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
65
+ if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
66
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)}GB`;
67
+ }
68
+ function isStreamGuardError(error) {
69
+ return error instanceof StreamGuardError;
70
+ }
71
+ function isStreamTimeoutError(error) {
72
+ return error instanceof StreamTimeoutError;
73
+ }
74
+ function isStreamStalledError(error) {
75
+ return error instanceof StreamStalledError;
76
+ }
77
+ function isStreamLimitError(error) {
78
+ return error instanceof StreamLimitError;
79
+ }
80
+ function isStreamHeapError(error) {
81
+ return error instanceof StreamHeapError;
82
+ }
83
+
84
+ // src/index.ts
85
+ var GUARD_METADATA = /* @__PURE__ */ Symbol("stream-guard:metadata");
86
+ function isGuarded(stream) {
87
+ return GUARD_METADATA in stream;
88
+ }
89
+ function getGuardMetadata(stream) {
90
+ return stream[GUARD_METADATA];
91
+ }
92
+ function generateGuardId() {
93
+ return `guard_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
94
+ }
95
+ function getChunkSize(chunk) {
96
+ if (Buffer.isBuffer(chunk)) {
97
+ return chunk.length;
98
+ }
99
+ if (typeof chunk === "string") {
100
+ return Buffer.byteLength(chunk);
101
+ }
102
+ if (chunk instanceof Uint8Array) {
103
+ return chunk.byteLength;
104
+ }
105
+ if (ArrayBuffer.isView(chunk)) {
106
+ return chunk.byteLength;
107
+ }
108
+ if (chunk !== null && typeof chunk === "object") {
109
+ try {
110
+ return Buffer.byteLength(JSON.stringify(chunk));
111
+ } catch {
112
+ return 0;
113
+ }
114
+ }
115
+ return 0;
116
+ }
117
+ function safeDestroy(stream, error) {
118
+ const s = stream;
119
+ try {
120
+ if (typeof s.destroy === "function") {
121
+ s.destroy(error);
122
+ } else if (typeof s.end === "function") {
123
+ s.end();
124
+ s.emit("error", error);
125
+ } else {
126
+ s.emit("error", error);
127
+ }
128
+ } catch {
129
+ }
130
+ }
131
+ function guard(stream, options = {}) {
132
+ if (options.timeout !== void 0 && (options.timeout <= 0 || !Number.isFinite(options.timeout))) {
133
+ throw new TypeError("timeout must be a positive finite number");
134
+ }
135
+ if (options.stalled !== void 0 && (options.stalled <= 0 || !Number.isFinite(options.stalled))) {
136
+ throw new TypeError("stalled must be a positive finite number");
137
+ }
138
+ if (options.maxBytes !== void 0 && (options.maxBytes <= 0 || !Number.isFinite(options.maxBytes))) {
139
+ throw new TypeError("maxBytes must be a positive finite number");
140
+ }
141
+ if (options.maxHeap !== void 0 && (options.maxHeap <= 0 || !Number.isFinite(options.maxHeap))) {
142
+ throw new TypeError("maxHeap must be a positive finite number");
143
+ }
144
+ if (isGuarded(stream)) {
145
+ throw new Error("Stream is already guarded. Call release() first to re-guard.");
146
+ }
147
+ const {
148
+ timeout,
149
+ stalled,
150
+ maxBytes,
151
+ maxHeap,
152
+ heapCheckInterval = 100,
153
+ heapCheckChunks,
154
+ onDestroy
155
+ } = options;
156
+ const guardId = generateGuardId();
157
+ const state = {
158
+ startTime: Date.now(),
159
+ lastActivityTime: Date.now(),
160
+ bytesTransferred: 0,
161
+ chunksSinceHeapCheck: 0,
162
+ isDestroyed: false,
163
+ isCompleted: false
164
+ };
165
+ const timers = {
166
+ absoluteTimeout: null,
167
+ stallInterval: null,
168
+ heapInterval: null
169
+ };
170
+ function cleanup() {
171
+ if (timers.absoluteTimeout) {
172
+ clearTimeout(timers.absoluteTimeout);
173
+ timers.absoluteTimeout = null;
174
+ }
175
+ if (timers.stallInterval) {
176
+ clearInterval(timers.stallInterval);
177
+ timers.stallInterval = null;
178
+ }
179
+ if (timers.heapInterval) {
180
+ clearInterval(timers.heapInterval);
181
+ timers.heapInterval = null;
182
+ }
183
+ }
184
+ function destroyWithError(error) {
185
+ if (state.isDestroyed || state.isCompleted) {
186
+ return;
187
+ }
188
+ state.isDestroyed = true;
189
+ cleanup();
190
+ if (onDestroy) {
191
+ try {
192
+ onDestroy(error);
193
+ } catch {
194
+ }
195
+ }
196
+ safeDestroy(stream, error);
197
+ }
198
+ function recordActivity() {
199
+ state.lastActivityTime = Date.now();
200
+ }
201
+ function processChunk(chunk) {
202
+ if (state.isDestroyed || state.isCompleted) {
203
+ return;
204
+ }
205
+ recordActivity();
206
+ const chunkSize = getChunkSize(chunk);
207
+ state.bytesTransferred += chunkSize;
208
+ if (maxBytes !== void 0 && state.bytesTransferred > maxBytes) {
209
+ destroyWithError(new StreamLimitError(maxBytes, state.bytesTransferred));
210
+ return;
211
+ }
212
+ if (maxHeap !== void 0 && heapCheckChunks !== void 0) {
213
+ state.chunksSinceHeapCheck++;
214
+ if (state.chunksSinceHeapCheck >= heapCheckChunks) {
215
+ state.chunksSinceHeapCheck = 0;
216
+ checkHeap();
217
+ }
218
+ }
219
+ }
220
+ function checkHeap() {
221
+ if (maxHeap === void 0 || state.isDestroyed || state.isCompleted) {
222
+ return;
223
+ }
224
+ const heapUsed = process.memoryUsage().heapUsed;
225
+ if (heapUsed > maxHeap) {
226
+ destroyWithError(new StreamHeapError(maxHeap, heapUsed));
227
+ }
228
+ }
229
+ function handleComplete() {
230
+ if (state.isCompleted) {
231
+ return;
232
+ }
233
+ state.isCompleted = true;
234
+ cleanup();
235
+ }
236
+ if (timeout !== void 0) {
237
+ timers.absoluteTimeout = setTimeout(() => {
238
+ const elapsed = Date.now() - state.startTime;
239
+ destroyWithError(new StreamTimeoutError(timeout, elapsed));
240
+ }, timeout);
241
+ if (timers.absoluteTimeout.unref) {
242
+ timers.absoluteTimeout.unref();
243
+ }
244
+ }
245
+ if (stalled !== void 0) {
246
+ timers.stallInterval = setInterval(() => {
247
+ if (state.isDestroyed || state.isCompleted) {
248
+ return;
249
+ }
250
+ const idleTime = Date.now() - state.lastActivityTime;
251
+ if (idleTime >= stalled) {
252
+ destroyWithError(new StreamStalledError(stalled, idleTime));
253
+ }
254
+ }, Math.min(stalled / 2, 1e3));
255
+ if (timers.stallInterval.unref) {
256
+ timers.stallInterval.unref();
257
+ }
258
+ }
259
+ if (maxHeap !== void 0) {
260
+ timers.heapInterval = setInterval(() => {
261
+ checkHeap();
262
+ }, heapCheckInterval);
263
+ if (timers.heapInterval.unref) {
264
+ timers.heapInterval.unref();
265
+ }
266
+ }
267
+ const emitter = stream;
268
+ emitter.on("data", processChunk);
269
+ emitter.on("drain", recordActivity);
270
+ emitter.on("end", handleComplete);
271
+ emitter.on("finish", handleComplete);
272
+ emitter.on("close", handleComplete);
273
+ emitter.on("error", handleComplete);
274
+ const metadata = {
275
+ guardId,
276
+ startTime: state.startTime,
277
+ getBytesTransferred: () => state.bytesTransferred,
278
+ getElapsedTime: () => Date.now() - state.startTime,
279
+ isActive: () => !state.isDestroyed && !state.isCompleted,
280
+ release: () => {
281
+ cleanup();
282
+ delete stream[GUARD_METADATA];
283
+ }
284
+ };
285
+ Object.defineProperty(stream, GUARD_METADATA, {
286
+ value: metadata,
287
+ writable: false,
288
+ enumerable: false,
289
+ configurable: true
290
+ });
291
+ return stream;
292
+ }
293
+ function createGuard(defaultOptions) {
294
+ return (stream, options = {}) => {
295
+ return guard(stream, { ...defaultOptions, ...options });
296
+ };
297
+ }
298
+ var index_default = guard;
299
+ export {
300
+ StreamGuardError,
301
+ StreamHeapError,
302
+ StreamLimitError,
303
+ StreamStalledError,
304
+ StreamTimeoutError,
305
+ createGuard,
306
+ index_default as default,
307
+ getGuardMetadata,
308
+ guard,
309
+ isGuarded,
310
+ isStreamGuardError,
311
+ isStreamHeapError,
312
+ isStreamLimitError,
313
+ isStreamStalledError,
314
+ isStreamTimeoutError
315
+ };
316
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/index.ts"],"sourcesContent":["/**\r\n * Base error class for all stream-guard errors.\r\n * Provides consistent error structure and metadata.\r\n */\r\nexport abstract class StreamGuardError extends Error {\r\n /** Timestamp when the error occurred */\r\n public readonly timestamp: number;\r\n \r\n /** The constraint that was violated */\r\n public abstract readonly constraint: string;\r\n\r\n constructor(message: string) {\r\n super(message);\r\n this.name = this.constructor.name;\r\n this.timestamp = Date.now();\r\n \r\n // Maintains proper stack trace for where error was thrown (V8 engines)\r\n if (Error.captureStackTrace) {\r\n Error.captureStackTrace(this, this.constructor);\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Thrown when a stream exceeds the absolute timeout limit.\r\n * \r\n * @example\r\n * ```ts\r\n * stream.on('error', (err) => {\r\n * if (err instanceof StreamTimeoutError) {\r\n * console.log(`Stream timed out after ${err.timeoutMs}ms`);\r\n * }\r\n * });\r\n * ```\r\n */\r\nexport class StreamTimeoutError extends StreamGuardError {\r\n public readonly constraint = 'timeout';\r\n \r\n /** The timeout limit that was exceeded (in milliseconds) */\r\n public readonly timeoutMs: number;\r\n \r\n /** Actual elapsed time when the timeout was triggered */\r\n public readonly elapsedMs: number;\r\n\r\n constructor(timeoutMs: number, elapsedMs: number) {\r\n super(`Stream exceeded absolute timeout of ${timeoutMs}ms (elapsed: ${elapsedMs}ms)`);\r\n this.timeoutMs = timeoutMs;\r\n this.elapsedMs = elapsedMs;\r\n }\r\n}\r\n\r\n/**\r\n * Thrown when a stream stalls (no data flow) for too long.\r\n * This typically indicates a zombie connection or slow loris attack.\r\n * \r\n * @example\r\n * ```ts\r\n * stream.on('error', (err) => {\r\n * if (err instanceof StreamStalledError) {\r\n * console.log(`Stream stalled for ${err.stalledMs}ms`);\r\n * }\r\n * });\r\n * ```\r\n */\r\nexport class StreamStalledError extends StreamGuardError {\r\n public readonly constraint = 'stalled';\r\n \r\n /** The stall threshold that was exceeded (in milliseconds) */\r\n public readonly stalledMs: number;\r\n \r\n /** Time since last data activity */\r\n public readonly idleMs: number;\r\n\r\n constructor(stalledMs: number, idleMs: number) {\r\n super(`Stream stalled: no data flow for ${idleMs}ms (limit: ${stalledMs}ms)`);\r\n this.stalledMs = stalledMs;\r\n this.idleMs = idleMs;\r\n }\r\n}\r\n\r\n/**\r\n * Thrown when a stream exceeds the maximum allowed bytes.\r\n * \r\n * @example\r\n * ```ts\r\n * stream.on('error', (err) => {\r\n * if (err instanceof StreamLimitError) {\r\n * console.log(`Stream exceeded ${err.maxBytes} bytes`);\r\n * }\r\n * });\r\n * ```\r\n */\r\nexport class StreamLimitError extends StreamGuardError {\r\n public readonly constraint = 'maxBytes';\r\n \r\n /** The maximum bytes limit that was exceeded */\r\n public readonly maxBytes: number;\r\n \r\n /** The actual number of bytes transferred */\r\n public readonly bytesTransferred: number;\r\n\r\n constructor(maxBytes: number, bytesTransferred: number) {\r\n super(`Stream exceeded byte limit: ${bytesTransferred} bytes transferred (limit: ${maxBytes} bytes)`);\r\n this.maxBytes = maxBytes;\r\n this.bytesTransferred = bytesTransferred;\r\n }\r\n}\r\n\r\n/**\r\n * Thrown when the process heap usage exceeds the configured limit during stream processing.\r\n * This is a safety mechanism to prevent OOM crashes.\r\n * \r\n * @example\r\n * ```ts\r\n * stream.on('error', (err) => {\r\n * if (err instanceof StreamHeapError) {\r\n * console.log(`Heap exceeded: ${err.heapUsed} / ${err.maxHeap}`);\r\n * }\r\n * });\r\n * ```\r\n */\r\nexport class StreamHeapError extends StreamGuardError {\r\n public readonly constraint = 'maxHeap';\r\n \r\n /** The maximum heap limit that was exceeded (in bytes) */\r\n public readonly maxHeap: number;\r\n \r\n /** The actual heap usage when the limit was exceeded (in bytes) */\r\n public readonly heapUsed: number;\r\n\r\n constructor(maxHeap: number, heapUsed: number) {\r\n super(`Process heap exceeded limit during stream: ${formatBytes(heapUsed)} used (limit: ${formatBytes(maxHeap)})`);\r\n this.maxHeap = maxHeap;\r\n this.heapUsed = heapUsed;\r\n }\r\n}\r\n\r\n/**\r\n * Utility function to format bytes into human-readable string.\r\n */\r\nfunction formatBytes(bytes: number): string {\r\n if (bytes < 1024) return `${bytes}B`;\r\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;\r\n if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;\r\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)}GB`;\r\n}\r\n\r\n/**\r\n * Type guard to check if an error is a StreamGuardError.\r\n */\r\nexport function isStreamGuardError(error: unknown): error is StreamGuardError {\r\n return error instanceof StreamGuardError;\r\n}\r\n\r\n/**\r\n * Type guard to check if an error is a StreamTimeoutError.\r\n */\r\nexport function isStreamTimeoutError(error: unknown): error is StreamTimeoutError {\r\n return error instanceof StreamTimeoutError;\r\n}\r\n\r\n/**\r\n * Type guard to check if an error is a StreamStalledError.\r\n */\r\nexport function isStreamStalledError(error: unknown): error is StreamStalledError {\r\n return error instanceof StreamStalledError;\r\n}\r\n\r\n/**\r\n * Type guard to check if an error is a StreamLimitError.\r\n */\r\nexport function isStreamLimitError(error: unknown): error is StreamLimitError {\r\n return error instanceof StreamLimitError;\r\n}\r\n\r\n/**\r\n * Type guard to check if an error is a StreamHeapError.\r\n */\r\nexport function isStreamHeapError(error: unknown): error is StreamHeapError {\r\n return error instanceof StreamHeapError;\r\n}\r\n","import type { Readable, Writable, Duplex, Transform } from 'node:stream';\r\nimport type { EventEmitter } from 'node:events';\r\n\r\nimport {\r\n StreamTimeoutError,\r\n StreamStalledError,\r\n StreamLimitError,\r\n StreamHeapError,\r\n} from './errors.js';\r\n\r\n// Re-export all error classes and type guards\r\nexport {\r\n StreamGuardError,\r\n StreamTimeoutError,\r\n StreamStalledError,\r\n StreamLimitError,\r\n StreamHeapError,\r\n isStreamGuardError,\r\n isStreamTimeoutError,\r\n isStreamStalledError,\r\n isStreamLimitError,\r\n isStreamHeapError,\r\n} from './errors.js';\r\n\r\n/**\r\n * Supported stream types that can be guarded.\r\n */\r\nexport type GuardableStream = (Readable | Writable | Duplex | Transform) & EventEmitter;\r\n\r\n/**\r\n * Internal interface for stream-like objects with destroy capability.\r\n */\r\ninterface DestroyableStream {\r\n destroy?(error?: Error): void;\r\n end?(): void;\r\n emit(event: string | symbol, ...args: unknown[]): boolean;\r\n on(event: string | symbol, listener: (...args: unknown[]) => void): unknown;\r\n}\r\n\r\n/**\r\n * Configuration options for the stream guard.\r\n */\r\nexport interface GuardOptions {\r\n /**\r\n * Absolute timeout in milliseconds.\r\n * If the stream doesn't complete within this time, it will be destroyed.\r\n * \r\n * @example\r\n * ```ts\r\n * guard(stream, { timeout: 30000 }); // 30 second timeout\r\n * ```\r\n */\r\n timeout?: number;\r\n\r\n /**\r\n * Stall protection timeout in milliseconds.\r\n * If no data flows for this duration, the stream will be destroyed.\r\n * Useful for detecting zombie connections and slow loris attacks.\r\n * \r\n * @example\r\n * ```ts\r\n * guard(stream, { stalled: 5000 }); // Kill if no data for 5 seconds\r\n * ```\r\n */\r\n stalled?: number;\r\n\r\n /**\r\n * Maximum bytes allowed through the stream.\r\n * If exceeded, the stream will be destroyed.\r\n * \r\n * @example\r\n * ```ts\r\n * guard(stream, { maxBytes: 10 * 1024 * 1024 }); // 10MB limit\r\n * ```\r\n */\r\n maxBytes?: number;\r\n\r\n /**\r\n * Maximum heap memory usage in bytes.\r\n * If process.memoryUsage().heapUsed exceeds this during stream processing,\r\n * the stream will be destroyed to prevent OOM crashes.\r\n * \r\n * @example\r\n * ```ts\r\n * guard(stream, { maxHeap: 500 * 1024 * 1024 }); // 500MB heap limit\r\n * ```\r\n */\r\n maxHeap?: number;\r\n\r\n /**\r\n * Interval in milliseconds for heap memory checks.\r\n * Lower values provide faster detection but use more CPU.\r\n * \r\n * @default 100\r\n */\r\n heapCheckInterval?: number;\r\n\r\n /**\r\n * Number of chunks between heap checks.\r\n * Alternative to time-based heap checking.\r\n * If both are set, checks happen on whichever triggers first.\r\n * \r\n * @default undefined (time-based only)\r\n */\r\n heapCheckChunks?: number;\r\n\r\n /**\r\n * Called when the stream is destroyed by the guard.\r\n * Useful for logging and monitoring.\r\n */\r\n onDestroy?: (error: Error) => void;\r\n}\r\n\r\n/**\r\n * Internal state for tracking stream metrics.\r\n */\r\ninterface GuardState {\r\n startTime: number;\r\n lastActivityTime: number;\r\n bytesTransferred: number;\r\n chunksSinceHeapCheck: number;\r\n isDestroyed: boolean;\r\n isCompleted: boolean;\r\n}\r\n\r\n/**\r\n * Internal timers and cleanup references.\r\n */\r\ninterface GuardTimers {\r\n absoluteTimeout: ReturnType<typeof setTimeout> | null;\r\n stallInterval: ReturnType<typeof setInterval> | null;\r\n heapInterval: ReturnType<typeof setInterval> | null;\r\n}\r\n\r\n/**\r\n * Metadata attached to guarded streams.\r\n */\r\nexport interface GuardMetadata {\r\n /** Unique guard instance ID */\r\n readonly guardId: string;\r\n /** When the guard was attached */\r\n readonly startTime: number;\r\n /** Get current bytes transferred */\r\n getBytesTransferred(): number;\r\n /** Get elapsed time in milliseconds */\r\n getElapsedTime(): number;\r\n /** Check if guard is still active */\r\n isActive(): boolean;\r\n /** Manually release all guards and timers */\r\n release(): void;\r\n}\r\n\r\n// Symbol for attaching metadata without polluting the stream interface\r\nconst GUARD_METADATA = Symbol('stream-guard:metadata');\r\n\r\n/**\r\n * Helper type for streams with symbol properties\r\n */\r\ntype StreamWithMetadata = GuardableStream & { [key: symbol]: unknown };\r\n\r\n/**\r\n * Check if a stream has already been guarded.\r\n */\r\nexport function isGuarded(stream: GuardableStream): boolean {\r\n return GUARD_METADATA in stream;\r\n}\r\n\r\n/**\r\n * Get the guard metadata from a guarded stream.\r\n * Returns undefined if the stream is not guarded.\r\n */\r\nexport function getGuardMetadata(stream: GuardableStream): GuardMetadata | undefined {\r\n return (stream as unknown as StreamWithMetadata)[GUARD_METADATA] as GuardMetadata | undefined;\r\n}\r\n\r\n/**\r\n * Generate a unique guard ID.\r\n */\r\nfunction generateGuardId(): string {\r\n return `guard_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;\r\n}\r\n\r\n/**\r\n * Get the byte length of a chunk.\r\n */\r\nfunction getChunkSize(chunk: unknown): number {\r\n if (Buffer.isBuffer(chunk)) {\r\n return chunk.length;\r\n }\r\n if (typeof chunk === 'string') {\r\n return Buffer.byteLength(chunk);\r\n }\r\n if (chunk instanceof Uint8Array) {\r\n return chunk.byteLength;\r\n }\r\n if (ArrayBuffer.isView(chunk)) {\r\n return chunk.byteLength;\r\n }\r\n // For object mode streams, estimate size via JSON (rough estimate)\r\n if (chunk !== null && typeof chunk === 'object') {\r\n try {\r\n return Buffer.byteLength(JSON.stringify(chunk));\r\n } catch {\r\n return 0;\r\n }\r\n }\r\n return 0;\r\n}\r\n\r\n/**\r\n * Safely destroy a stream with an error.\r\n */\r\nfunction safeDestroy(stream: GuardableStream, error: Error): void {\r\n const s = stream as DestroyableStream;\r\n try {\r\n if (typeof s.destroy === 'function') {\r\n s.destroy(error);\r\n } else if (typeof s.end === 'function') {\r\n // Fallback for streams without destroy\r\n s.end();\r\n s.emit('error', error);\r\n } else {\r\n s.emit('error', error);\r\n }\r\n } catch {\r\n // Stream may already be destroyed, ignore errors\r\n }\r\n}\r\n\r\n/**\r\n * Guards a Node.js stream with safety constraints.\r\n * \r\n * Wraps any Readable, Writable, Duplex, or Transform stream and enforces:\r\n * - Absolute timeout (total time to complete)\r\n * - Stall detection (idle time without data flow)\r\n * - Byte limits (maximum data throughput)\r\n * - Heap memory limits (prevent OOM)\r\n * \r\n * @param stream - The stream to guard\r\n * @param options - Guard configuration options\r\n * @returns The same stream with guards attached\r\n * \r\n * @example\r\n * ```ts\r\n * import { guard, StreamTimeoutError } from 'stream-guard';\r\n * import { createReadStream } from 'node:fs';\r\n * \r\n * const stream = guard(createReadStream('file.txt'), {\r\n * timeout: 30000, // 30s absolute timeout\r\n * stalled: 5000, // 5s stall detection\r\n * maxBytes: 10 * 1024 * 1024, // 10MB limit\r\n * });\r\n * \r\n * stream.on('error', (err) => {\r\n * if (err instanceof StreamTimeoutError) {\r\n * console.log('Stream timed out!');\r\n * }\r\n * });\r\n * \r\n * stream.pipe(destination);\r\n * ```\r\n */\r\nexport function guard<T extends GuardableStream>(stream: T, options: GuardOptions = {}): T {\r\n // Validate options\r\n if (options.timeout !== undefined && (options.timeout <= 0 || !Number.isFinite(options.timeout))) {\r\n throw new TypeError('timeout must be a positive finite number');\r\n }\r\n if (options.stalled !== undefined && (options.stalled <= 0 || !Number.isFinite(options.stalled))) {\r\n throw new TypeError('stalled must be a positive finite number');\r\n }\r\n if (options.maxBytes !== undefined && (options.maxBytes <= 0 || !Number.isFinite(options.maxBytes))) {\r\n throw new TypeError('maxBytes must be a positive finite number');\r\n }\r\n if (options.maxHeap !== undefined && (options.maxHeap <= 0 || !Number.isFinite(options.maxHeap))) {\r\n throw new TypeError('maxHeap must be a positive finite number');\r\n }\r\n\r\n // Check if already guarded\r\n if (isGuarded(stream)) {\r\n throw new Error('Stream is already guarded. Call release() first to re-guard.');\r\n }\r\n\r\n const {\r\n timeout,\r\n stalled,\r\n maxBytes,\r\n maxHeap,\r\n heapCheckInterval = 100,\r\n heapCheckChunks,\r\n onDestroy,\r\n } = options;\r\n\r\n const guardId = generateGuardId();\r\n\r\n // Initialize state\r\n const state: GuardState = {\r\n startTime: Date.now(),\r\n lastActivityTime: Date.now(),\r\n bytesTransferred: 0,\r\n chunksSinceHeapCheck: 0,\r\n isDestroyed: false,\r\n isCompleted: false,\r\n };\r\n\r\n // Initialize timers\r\n const timers: GuardTimers = {\r\n absoluteTimeout: null,\r\n stallInterval: null,\r\n heapInterval: null,\r\n };\r\n\r\n /**\r\n * Clean up all timers and listeners.\r\n */\r\n function cleanup(): void {\r\n if (timers.absoluteTimeout) {\r\n clearTimeout(timers.absoluteTimeout);\r\n timers.absoluteTimeout = null;\r\n }\r\n if (timers.stallInterval) {\r\n clearInterval(timers.stallInterval);\r\n timers.stallInterval = null;\r\n }\r\n if (timers.heapInterval) {\r\n clearInterval(timers.heapInterval);\r\n timers.heapInterval = null;\r\n }\r\n }\r\n\r\n /**\r\n * Handle stream destruction by guard.\r\n */\r\n function destroyWithError(error: Error): void {\r\n if (state.isDestroyed || state.isCompleted) {\r\n return;\r\n }\r\n state.isDestroyed = true;\r\n cleanup();\r\n \r\n if (onDestroy) {\r\n try {\r\n onDestroy(error);\r\n } catch {\r\n // Ignore callback errors\r\n }\r\n }\r\n \r\n safeDestroy(stream, error);\r\n }\r\n\r\n /**\r\n * Mark activity timestamp.\r\n */\r\n function recordActivity(): void {\r\n state.lastActivityTime = Date.now();\r\n }\r\n\r\n /**\r\n * Handle chunk processing for byte counting and heap checks.\r\n */\r\n function processChunk(chunk: unknown): void {\r\n if (state.isDestroyed || state.isCompleted) {\r\n return;\r\n }\r\n\r\n recordActivity();\r\n\r\n // Track bytes\r\n const chunkSize = getChunkSize(chunk);\r\n state.bytesTransferred += chunkSize;\r\n\r\n // Check byte limit\r\n if (maxBytes !== undefined && state.bytesTransferred > maxBytes) {\r\n destroyWithError(new StreamLimitError(maxBytes, state.bytesTransferred));\r\n return;\r\n }\r\n\r\n // Check heap on chunk interval if configured\r\n if (maxHeap !== undefined && heapCheckChunks !== undefined) {\r\n state.chunksSinceHeapCheck++;\r\n if (state.chunksSinceHeapCheck >= heapCheckChunks) {\r\n state.chunksSinceHeapCheck = 0;\r\n checkHeap();\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Check heap memory usage.\r\n */\r\n function checkHeap(): void {\r\n if (maxHeap === undefined || state.isDestroyed || state.isCompleted) {\r\n return;\r\n }\r\n\r\n const heapUsed = process.memoryUsage().heapUsed;\r\n if (heapUsed > maxHeap) {\r\n destroyWithError(new StreamHeapError(maxHeap, heapUsed));\r\n }\r\n }\r\n\r\n /**\r\n * Handle stream completion.\r\n */\r\n function handleComplete(): void {\r\n if (state.isCompleted) {\r\n return;\r\n }\r\n state.isCompleted = true;\r\n cleanup();\r\n }\r\n\r\n // Set up absolute timeout\r\n if (timeout !== undefined) {\r\n timers.absoluteTimeout = setTimeout(() => {\r\n const elapsed = Date.now() - state.startTime;\r\n destroyWithError(new StreamTimeoutError(timeout, elapsed));\r\n }, timeout);\r\n \r\n // Unref to not keep the process alive\r\n if (timers.absoluteTimeout.unref) {\r\n timers.absoluteTimeout.unref();\r\n }\r\n }\r\n\r\n // Set up stall detection\r\n if (stalled !== undefined) {\r\n timers.stallInterval = setInterval(() => {\r\n if (state.isDestroyed || state.isCompleted) {\r\n return;\r\n }\r\n \r\n const idleTime = Date.now() - state.lastActivityTime;\r\n if (idleTime >= stalled) {\r\n destroyWithError(new StreamStalledError(stalled, idleTime));\r\n }\r\n }, Math.min(stalled / 2, 1000)); // Check at half the stall interval, max every second\r\n \r\n if (timers.stallInterval.unref) {\r\n timers.stallInterval.unref();\r\n }\r\n }\r\n\r\n // Set up heap monitoring interval\r\n if (maxHeap !== undefined) {\r\n timers.heapInterval = setInterval(() => {\r\n checkHeap();\r\n }, heapCheckInterval);\r\n \r\n if (timers.heapInterval.unref) {\r\n timers.heapInterval.unref();\r\n }\r\n }\r\n\r\n // Cast stream to use generic event emitter interface\r\n const emitter = stream as DestroyableStream;\r\n\r\n // Listen to data events (for Readable streams)\r\n emitter.on('data', processChunk);\r\n\r\n // Listen to drain events (for Writable streams)\r\n emitter.on('drain', recordActivity);\r\n\r\n // Listen to completion events\r\n emitter.on('end', handleComplete);\r\n emitter.on('finish', handleComplete);\r\n emitter.on('close', handleComplete);\r\n emitter.on('error', handleComplete);\r\n\r\n // Attach metadata\r\n const metadata: GuardMetadata = {\r\n guardId,\r\n startTime: state.startTime,\r\n getBytesTransferred: () => state.bytesTransferred,\r\n getElapsedTime: () => Date.now() - state.startTime,\r\n isActive: () => !state.isDestroyed && !state.isCompleted,\r\n release: () => {\r\n cleanup();\r\n delete (stream as unknown as StreamWithMetadata)[GUARD_METADATA];\r\n },\r\n };\r\n\r\n Object.defineProperty(stream, GUARD_METADATA, {\r\n value: metadata,\r\n writable: false,\r\n enumerable: false,\r\n configurable: true,\r\n });\r\n\r\n return stream;\r\n}\r\n\r\n/**\r\n * Creates a pre-configured guard factory with default options.\r\n * Useful for applying consistent settings across multiple streams.\r\n * \r\n * @param defaultOptions - Default options for all guards created by this factory\r\n * @returns A guard function with defaults pre-applied\r\n * \r\n * @example\r\n * ```ts\r\n * const apiGuard = createGuard({\r\n * timeout: 30000,\r\n * maxBytes: 5 * 1024 * 1024,\r\n * });\r\n * \r\n * // All streams will have these defaults\r\n * apiGuard(stream1);\r\n * apiGuard(stream2, { stalled: 5000 }); // Can override\r\n * ```\r\n */\r\nexport function createGuard(defaultOptions: GuardOptions): <T extends GuardableStream>(stream: T, options?: GuardOptions) => T {\r\n return <T extends GuardableStream>(stream: T, options: GuardOptions = {}): T => {\r\n return guard(stream, { ...defaultOptions, ...options });\r\n };\r\n}\r\n\r\n// Default export for convenience\r\nexport default guard;\r\n"],"mappings":"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","names":[]}