@fastls/fastls 0.0.0-stage → 0.10.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.
@@ -0,0 +1,455 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Session = void 0;
4
+ const node_fs_1 = require("node:fs");
5
+ const cookies_js_1 = require("./cookies.js");
6
+ const headers_js_1 = require("./headers.js");
7
+ const errors_js_1 = require("./errors.js");
8
+ const native_js_1 = require("./native.js");
9
+ const response_js_1 = require("./response.js");
10
+ const timeout_js_1 = require("./timeout.js");
11
+ const upload_js_1 = require("./upload.js");
12
+ const duplex_js_1 = require("./duplex.js");
13
+ const sse_js_1 = require("./sse.js");
14
+ const notifications_js_1 = require("./notifications.js");
15
+ const request_options_js_1 = require("./request-options.js");
16
+ const websocket_js_1 = require("./websocket.js");
17
+ const fetch_js_1 = require("./fetch.js");
18
+ function decoding(decode, maximum) {
19
+ if (typeof decode !== "boolean")
20
+ throw new TypeError("decodeContent must be a boolean");
21
+ if (!Number.isSafeInteger(maximum) || maximum < 1 || maximum > 2 ** 40)
22
+ throw new RangeError("maxDecodedBytes must be 1..1099511627776");
23
+ }
24
+ function proxyValue(value) {
25
+ if (value !== undefined && value !== null && typeof value !== "string")
26
+ throw new TypeError("proxy must be a URL string or null");
27
+ // The native validator owns URL parsing and redacted errors. Do not include
28
+ // credentials in JavaScript URL parser errors or normalize invalid input.
29
+ return value;
30
+ }
31
+ function proxyTrust(path) {
32
+ if (path === null || path === undefined)
33
+ return undefined;
34
+ if (typeof path !== "string")
35
+ throw new TypeError("proxyCaFile must be a path string or null");
36
+ const pem = (0, node_fs_1.readFileSync)(path, "utf8");
37
+ if (!pem.trim())
38
+ throw new TypeError("proxyCaFile is empty");
39
+ return pem;
40
+ }
41
+ function mergeHeaders(...sources) {
42
+ const merged = Object.create(null);
43
+ for (const source of sources) {
44
+ for (const [key, value] of Object.entries(source ?? {})) {
45
+ if (typeof value !== "string")
46
+ throw new TypeError("header values must be strings");
47
+ merged[key.toLowerCase()] = value;
48
+ }
49
+ }
50
+ return merged;
51
+ }
52
+ class Session {
53
+ /** Construct and initialize; failed initialization closes the new session. */
54
+ static async open(options = {}) {
55
+ const session = new Session(options);
56
+ try {
57
+ await session.ready();
58
+ return session;
59
+ }
60
+ catch (error) {
61
+ await session.closeAsync();
62
+ throw error;
63
+ }
64
+ }
65
+ notificationMode;
66
+ cookies;
67
+ native;
68
+ handle;
69
+ profile;
70
+ proxy;
71
+ proxyCaPem;
72
+ headers;
73
+ timeouts;
74
+ decodeContent;
75
+ maxDecodedBytes;
76
+ caPem;
77
+ active = new Set();
78
+ websockets = new Set();
79
+ producers = new Set();
80
+ closed = false;
81
+ notifier;
82
+ setup;
83
+ constructor(options = {}) {
84
+ this.proxy = proxyValue(options.proxy);
85
+ this.proxyCaPem = proxyTrust(options.proxyCaFile);
86
+ const notifications = options.notifications === undefined ? "auto" : options.notifications;
87
+ if (!["auto", "required", "poll"].includes(notifications))
88
+ throw new TypeError("notifications must be auto, required or poll");
89
+ this.profile = options.profile ?? "chrome/156/windows";
90
+ this.timeouts = (0, timeout_js_1.timeoutFields)(options.timeoutMs ?? 30000);
91
+ this.decodeContent = options.decodeContent ?? true;
92
+ this.maxDecodedBytes = options.maxDecodedBytes ?? 256 * 1024 * 1024;
93
+ decoding(this.decodeContent, this.maxDecodedBytes);
94
+ this.headers = mergeHeaders(options.headers);
95
+ if (options.cookies !== undefined && typeof options.cookies !== "boolean")
96
+ throw new TypeError("cookies must be a boolean");
97
+ this.caPem = options.caFile === undefined ? undefined : (0, node_fs_1.readFileSync)(options.caFile, "utf8");
98
+ if (this.caPem !== undefined && !this.caPem.trim())
99
+ throw new TypeError("caFile is empty");
100
+ this.native = native_js_1.Native.load(options.library);
101
+ const supported = this.native.notificationsAvailable && this.native.call("features").includes("session_notifications");
102
+ if (notifications === "required" && !supported)
103
+ throw new errors_js_1.FastlsError("notifications require a newer Fastls native library");
104
+ this.notificationMode = notifications !== "poll" && supported ? "async-events" : "poll";
105
+ if (!this.native.call("features").includes("transport_context_v1"))
106
+ throw new errors_js_1.FastlsError("SDK-owned sessions require the Fastls 0.1.4 native library or newer");
107
+ this.handle = (0, native_js_1.asHandle)(this.native.call("transport_create"));
108
+ this.cookies = new cookies_js_1.Cookies(() => this.ensureOpen(), options.cookies !== false);
109
+ }
110
+ ensureOpen() {
111
+ if (this.closed)
112
+ throw new errors_js_1.FastlsError("session is closed");
113
+ }
114
+ /** Initialize the shared wakeup channel before any HTTP request is allocated. */
115
+ async ready(signal) {
116
+ this.ensureOpen();
117
+ if (signal !== undefined && !(signal instanceof AbortSignal))
118
+ throw new TypeError("signal must be an AbortSignal");
119
+ if (signal?.aborted)
120
+ throw new errors_js_1.AbortError(undefined, { cause: signal.reason });
121
+ if (this.notificationMode === "poll")
122
+ return;
123
+ if (!this.notifier) {
124
+ const notifier = this.notifier = new notifications_js_1.Notifier(this.native, this.handle);
125
+ const setup = this.setup = notifier.start().catch(async (error) => {
126
+ await notifier.closeAsync();
127
+ if (this.notifier === notifier) {
128
+ this.notifier = undefined;
129
+ this.setup = undefined;
130
+ }
131
+ throw error;
132
+ });
133
+ void setup.catch(() => { });
134
+ }
135
+ const notifier = this.notifier;
136
+ notifier.setupWaiters++;
137
+ try {
138
+ if (signal)
139
+ await (0, upload_js_1.abortable)(this.setup, signal);
140
+ else
141
+ await this.setup;
142
+ this.ensureOpen();
143
+ notifier.check();
144
+ }
145
+ catch (error) {
146
+ if (signal?.aborted)
147
+ throw new errors_js_1.AbortError(undefined, { cause: signal.reason });
148
+ throw error;
149
+ }
150
+ finally {
151
+ if (--notifier.setupWaiters === 0 && !notifier.isReady && this.notifier === notifier) {
152
+ notifier.close();
153
+ this.notifier = undefined;
154
+ this.setup = undefined;
155
+ }
156
+ }
157
+ }
158
+ begin(method, url, options, manual = false, completeHeaders = false) {
159
+ this.ensureOpen();
160
+ if (options.signal !== undefined && !(options.signal instanceof AbortSignal)) {
161
+ throw new TypeError("signal must be an AbortSignal");
162
+ }
163
+ if (options.signal?.aborted)
164
+ throw new errors_js_1.AbortError(undefined, { cause: options.signal.reason });
165
+ const headers = mergeHeaders(completeHeaders ? undefined : this.headers, options.headers);
166
+ for (const name of ["proxy-authorization", "proxy-connection"]) {
167
+ if (name in headers)
168
+ throw new errors_js_1.FastlsError("proxy headers must not be sent as target headers; configure proxy URL credentials");
169
+ }
170
+ const proxy = proxyValue(options.proxy === undefined ? this.proxy : options.proxy);
171
+ if (proxy && !this.native.call("features").includes("proxy_tunnels")) {
172
+ throw new errors_js_1.FastlsError("proxy support requires a newer Fastls native library");
173
+ }
174
+ const httpsProxy = proxy?.toLowerCase().startsWith("https://") ?? false;
175
+ if (httpsProxy && !this.native.call("features").includes("https_proxy_tls")) {
176
+ throw new errors_js_1.FastlsError("HTTPS proxy support requires a newer Fastls native library");
177
+ }
178
+ const proxyCaPem = !httpsProxy ? undefined : options.proxyCaFile === undefined
179
+ ? this.proxyCaPem : proxyTrust(options.proxyCaFile);
180
+ if (options.duplex !== undefined && typeof options.duplex !== "boolean")
181
+ throw new TypeError("duplex must be a boolean");
182
+ const duplex = options.duplex ?? false;
183
+ if (duplex && !this.native.call("features").includes("duplex_h2")) {
184
+ throw new errors_js_1.FastlsError("HTTP/2 duplex requires a newer Fastls native library");
185
+ }
186
+ const prepared = (0, request_options_js_1.prepare)(url, options, headers);
187
+ url = prepared.url;
188
+ if (!("cookie" in headers)) {
189
+ const cookie = this.cookies.header(url);
190
+ if (cookie)
191
+ headers.cookie = cookie;
192
+ }
193
+ const body = prepared.body === undefined && duplex && !manual ? Buffer.alloc(0) : prepared.body;
194
+ const streaming = (0, upload_js_1.isStreamingBody)(body) || duplex;
195
+ const bodyBytes = streaming || body === undefined ? undefined : Buffer.from(body);
196
+ const timeouts = options.timeoutMs === undefined ? this.timeouts : (0, timeout_js_1.timeoutFields)(options.timeoutMs);
197
+ const decodeContent = options.decodeContent ?? this.decodeContent;
198
+ const maxDecodedBytes = options.maxDecodedBytes ?? this.maxDecodedBytes;
199
+ decoding(decodeContent, maxDecodedBytes);
200
+ if (options.allowRedirects !== undefined && typeof options.allowRedirects !== "boolean") {
201
+ throw new TypeError("allowRedirects must be a boolean");
202
+ }
203
+ const started = performance.now();
204
+ let trailers;
205
+ if (options.trailers !== undefined) {
206
+ if (!this.native.call("features").includes("request_trailers"))
207
+ throw new errors_js_1.FastlsError("request trailers require a newer Fastls native library");
208
+ if (!options.trailers || typeof options.trailers !== "object" || Array.isArray(options.trailers))
209
+ throw new TypeError("trailers must be a mapping");
210
+ trailers = {};
211
+ for (const [name, value] of Object.entries(options.trailers)) {
212
+ const values = typeof value === "string" ? [value] : value;
213
+ if (!Array.isArray(values) || values.some(item => typeof item !== "string"))
214
+ throw new TypeError("trailer values must be strings");
215
+ trailers[name] = [...values];
216
+ }
217
+ }
218
+ const handle = this.native.start({
219
+ session: this.handle, url, method, headers, trailers, proxy: proxy || undefined, proxy_ca_pem: proxyCaPem,
220
+ profile: options.profile ?? this.profile,
221
+ ...timeouts, decode_content: decodeContent, max_decoded_bytes: maxDecodedBytes,
222
+ disable_redirect: true,
223
+ ca_pem: this.caPem,
224
+ body_base64: bodyBytes?.toString("base64"),
225
+ stream_body: streaming || undefined,
226
+ duplex: duplex || undefined,
227
+ });
228
+ const transfer = new response_js_1.Transfer(this.native, handle, () => { this.active.delete(transfer); this.notifier?.forget(handle); }, options.signal, duplex, this.notifier, info => this.cookies.extract(info.final_url ?? url, new headers_js_1.Headers(info.header_values, info.headers).getAll("set-cookie")));
229
+ this.active.add(transfer);
230
+ try {
231
+ this.notifier?.track(handle);
232
+ transfer.attachSignal(timeouts.timeout_ms, started);
233
+ return { transfer, body, streaming };
234
+ }
235
+ catch (error) {
236
+ transfer.close();
237
+ throw error;
238
+ }
239
+ }
240
+ async streamHop(method, url, options) {
241
+ await this.ready(options.signal);
242
+ const { transfer, body, streaming } = this.begin(method, url, options, false, true);
243
+ try {
244
+ const upload = streaming ? (0, upload_js_1.startUpload)(transfer, body) : undefined;
245
+ if (upload) {
246
+ this.producers.add(upload);
247
+ void upload.cleanup.then(() => this.producers.delete(upload), () => this.producers.delete(upload));
248
+ }
249
+ let info;
250
+ try {
251
+ info = await transfer.headers();
252
+ if (!transfer.duplex && upload && info.status < 300 && !info.upload_discarded && !upload.complete()) {
253
+ throw new errors_js_1.FastlsError("server responded before upload finished; duplex streaming is not supported");
254
+ }
255
+ }
256
+ finally {
257
+ if (upload && (!transfer.duplex || !info || info.status >= 300)) {
258
+ upload.stop();
259
+ await upload.done;
260
+ }
261
+ }
262
+ transfer.ensureOpen();
263
+ const response = new response_js_1.StreamResponse(transfer, info, transfer.duplex ? upload : undefined);
264
+ return response;
265
+ }
266
+ catch (error) {
267
+ transfer.close();
268
+ throw error;
269
+ }
270
+ }
271
+ async stream(method, url, options = {}) {
272
+ const headers = mergeHeaders(this.headers, options.headers);
273
+ const prepared = (0, request_options_js_1.prepare)(url, options, headers);
274
+ url = prepared.url;
275
+ options = { ...options, headers, body: prepared.body };
276
+ for (const key of ["params", "json", "form", "files"])
277
+ delete options[key];
278
+ const maximum = options.maxRedirects ?? 30;
279
+ if (!Number.isSafeInteger(maximum) || maximum < 0)
280
+ throw new RangeError("maxRedirects must be a non-negative integer");
281
+ const policy = options.timeoutMs === undefined ? this.timeouts : (0, timeout_js_1.timeoutFields)(options.timeoutMs);
282
+ const deadline = performance.now() + policy.timeout_ms;
283
+ for (let redirects = 0;; redirects++) {
284
+ const remaining = Math.ceil(deadline - performance.now());
285
+ if (remaining <= 0)
286
+ throw new errors_js_1.FastlsTimeoutError("total deadline exceeded", "total");
287
+ options.timeoutMs = { total: remaining, connect: policy.connect_timeout_ms, write: policy.write_timeout_ms, headers: policy.headers_timeout_ms, read: policy.read_timeout_ms };
288
+ const response = await this.streamHop(method, url, options);
289
+ const location = response.headers.get("location");
290
+ if (options.duplex || options.allowRedirects === false || ![301, 302, 303, 307, 308].includes(response.status) || !location || ([307, 308].includes(response.status) && (0, upload_js_1.isStreamingBody)(options.body)))
291
+ return response;
292
+ try {
293
+ if (redirects >= maximum)
294
+ throw new errors_js_1.FastlsError("maximum redirects exceeded");
295
+ const previous = new URL(url), target = new URL(location, url);
296
+ if (!["http:", "https:"].includes(target.protocol) || target.username || target.password)
297
+ throw new errors_js_1.FastlsError("invalid redirect URL");
298
+ target.hash = "";
299
+ if (previous.origin !== target.origin)
300
+ for (const name of ["authorization", "cookie", "host"])
301
+ delete headers[name];
302
+ if (([302, 303].includes(response.status) && method.toUpperCase() !== "HEAD") || (response.status === 301 && method.toUpperCase() === "POST")) {
303
+ method = "GET";
304
+ delete options.body;
305
+ delete options.trailers;
306
+ for (const name of ["content-type", "content-length", "transfer-encoding"])
307
+ delete headers[name];
308
+ }
309
+ url = target.href;
310
+ await response.read();
311
+ }
312
+ finally {
313
+ await response.closeAsync();
314
+ }
315
+ }
316
+ }
317
+ async sse(method, url, options = {}) {
318
+ const { maxEventBytes = 1024 * 1024, lastEventId, ...request } = options;
319
+ (0, sse_js_1.validateSSEOptions)(maxEventBytes, lastEventId);
320
+ const headers = mergeHeaders({ accept: "text/event-stream", "cache-control": "no-cache" }, request.headers);
321
+ if (lastEventId !== undefined)
322
+ headers["last-event-id"] = lastEventId;
323
+ const response = await this.stream(method, url, { ...request, headers });
324
+ try {
325
+ return new sse_js_1.SSEStream(response, maxEventBytes, lastEventId);
326
+ }
327
+ catch (error) {
328
+ await response.closeAsync();
329
+ throw error;
330
+ }
331
+ }
332
+ async request(method, url, options = {}) {
333
+ const stream = await this.stream(method, url, options);
334
+ try {
335
+ const content = await stream.read();
336
+ return new response_js_1.Response(stream.status, stream.headers, stream.match, content, stream.wireHeaders, stream.decoded, stream.httpVersion, stream.url, stream.trailers);
337
+ }
338
+ finally {
339
+ await stream.closeAsync();
340
+ }
341
+ }
342
+ /** Open a manual exchange; send() and response reads can run concurrently. */
343
+ duplex(method, url, options = {}) {
344
+ if (["body", "duplex", "json", "form", "files"].some(key => key in options))
345
+ throw new TypeError("manual duplex uses send() and finishUpload(), not body, json, form, files or duplex options");
346
+ this.ensureOpen();
347
+ const cancellation = new AbortController();
348
+ const signal = options.signal === undefined ? cancellation.signal : AbortSignal.any([options.signal, cancellation.signal]);
349
+ const transfer = this.ready(signal).then(() => {
350
+ if (cancellation.signal.aborted)
351
+ throw new errors_js_1.FastlsError("exchange is closed");
352
+ return this.begin(method, url, { ...options, signal, duplex: true }, true).transfer;
353
+ }, error => { if (cancellation.signal.aborted)
354
+ throw new errors_js_1.FastlsError("exchange is closed"); throw error; });
355
+ return new duplex_js_1.DuplexExchange(transfer, () => cancellation.abort());
356
+ }
357
+ get(url, options) { return this.request("GET", url, options); }
358
+ post(url, options) { return this.request("POST", url, options); }
359
+ put(url, options) { return this.request("PUT", url, options); }
360
+ patch(url, options) { return this.request("PATCH", url, options); }
361
+ delete(url, options) { return this.request("DELETE", url, options); }
362
+ head(url, options = {}) { return this.request("HEAD", url, { allowRedirects: false, ...options }); }
363
+ options(url, options) { return this.request("OPTIONS", url, options); }
364
+ fetch(input, options = {}) {
365
+ if (options.library !== undefined || options.caFile !== undefined)
366
+ throw new TypeError("library and caFile must be configured on Session");
367
+ return (0, fetch_js_1.fetchWithSession)(this, input, options);
368
+ }
369
+ async websocket(url, options = {}) {
370
+ await this.ready(options.signal);
371
+ this.ensureOpen();
372
+ if (options.signal?.aborted)
373
+ throw new errors_js_1.AbortError(undefined, { cause: options.signal.reason });
374
+ if (!this.native.websocketAvailable)
375
+ throw new errors_js_1.FastlsError("WebSocket requires a newer Fastls native library");
376
+ const cookieMode = options.cookieMode === undefined ? "session" : options.cookieMode;
377
+ if (cookieMode !== "session" && cookieMode !== "manual")
378
+ throw new TypeError("cookieMode must be session or manual");
379
+ const features = this.native.call("features");
380
+ const sessionCookies = features.includes("websocket_cookie_control_v1") || features.includes("websocket_cookies");
381
+ if (cookieMode === "manual" && !sessionCookies && features.includes("websocket_cookies_v1"))
382
+ throw new errors_js_1.FastlsError("This native library cannot disable automatic WebSocket cookies; a newer library with websocket_cookie_control_v1 is required");
383
+ if (cookieMode === "session" && !sessionCookies)
384
+ throw new errors_js_1.FastlsError("WebSocket session cookies require a newer Fastls native library; use cookieMode manual for legacy behavior");
385
+ const maximum = (options.maxMessageBytes === undefined ? 1048576 : options.maxMessageBytes);
386
+ if (!Number.isSafeInteger(maximum) || maximum < 1 || maximum > 16777216)
387
+ throw new RangeError("maxMessageBytes must be 1..16777216");
388
+ const close = (0, websocket_js_1.wsBudget)((options.closeTimeoutMs === undefined ? 1000 : options.closeTimeoutMs), "closeTimeoutMs", 60000);
389
+ const headers = mergeHeaders(this.headers, options.headers), prepared = (0, request_options_js_1.prepare)(url, { params: options.params }, headers);
390
+ if (cookieMode === "session" && !("cookie" in headers)) {
391
+ const cookie = this.cookies.header(prepared.url);
392
+ if (cookie)
393
+ headers.cookie = cookie;
394
+ }
395
+ const proxy = proxyValue(options.proxy === undefined ? this.proxy : options.proxy);
396
+ const proxyCA = proxy?.toLowerCase().startsWith("https://") ? (options.proxyCaFile === undefined ? this.proxyCaPem : proxyTrust(options.proxyCaFile)) : undefined;
397
+ if (options.subprotocols !== undefined && (!Array.isArray(options.subprotocols) || options.subprotocols.some(p => typeof p !== "string")))
398
+ throw new TypeError("subprotocols must be an array of strings");
399
+ const handle = this.native.wsStart({ url: prepared.url, session: this.handle, disable_cookies: true, headers, profile: options.profile ?? this.profile, proxy: proxy || undefined, ca_pem: this.caPem, proxy_ca_pem: proxyCA, subprotocols: options.subprotocols ?? [], max_message_bytes: maximum,
400
+ timeout_ms: (0, websocket_js_1.wsBudget)((options.handshakeTimeoutMs === undefined ? 30000 : options.handshakeTimeoutMs), "handshakeTimeoutMs"), read_timeout_ms: options.readTimeoutMs === null || options.readTimeoutMs === undefined ? 0 : (0, websocket_js_1.wsBudget)(options.readTimeoutMs, "readTimeoutMs"), write_timeout_ms: (0, websocket_js_1.wsBudget)((options.writeTimeoutMs === undefined ? 30000 : options.writeTimeoutMs), "writeTimeoutMs") });
401
+ const ws = new websocket_js_1.WebSocket(this.native, handle, () => { this.websockets.delete(ws); this.notifier?.forget(handle); }, maximum, close, this.notifier, options.signal);
402
+ this.websockets.add(ws);
403
+ try {
404
+ this.notifier?.track(handle);
405
+ return await ws.opened();
406
+ }
407
+ catch (error) {
408
+ ws.abort();
409
+ throw error;
410
+ }
411
+ finally {
412
+ if (cookieMode === "session" && !this.closed) {
413
+ const info = ws.info;
414
+ if (info.status !== 101 || info.state === "headers" || info.state === "eof")
415
+ this.cookies.extract(prepared.url, ws.headers.getAll("set-cookie"));
416
+ }
417
+ }
418
+ }
419
+ listProfiles() { this.ensureOpen(); return this.native.call("profiles"); }
420
+ stats() { this.ensureOpen(); return this.native.call("stats"); }
421
+ closeIdleConnections() { this.ensureOpen(); this.native.call("session_close_idle", this.handle); }
422
+ close() {
423
+ if (this.closed)
424
+ return;
425
+ this.cookies.clear();
426
+ this.closed = true;
427
+ try {
428
+ for (const transfer of this.active)
429
+ transfer.close(new errors_js_1.FastlsError("session is closed"));
430
+ for (const ws of this.websockets)
431
+ ws.abort(new errors_js_1.FastlsError("session is closed"));
432
+ }
433
+ finally {
434
+ try {
435
+ this.notifier?.close();
436
+ }
437
+ finally {
438
+ this.native.call("session_close", this.handle);
439
+ }
440
+ }
441
+ }
442
+ /** Release native resources immediately, then wait for cooperative upload cleanup. */
443
+ async closeAsync() {
444
+ const producers = [...this.producers];
445
+ this.close();
446
+ await this.notifier?.closeAsync();
447
+ const results = await Promise.allSettled(producers.map(task => task.cleanup));
448
+ for (const result of results)
449
+ if (result.status === "rejected")
450
+ throw result.reason;
451
+ for (const task of producers)
452
+ task.check();
453
+ }
454
+ }
455
+ exports.Session = Session;
package/dist/sse.d.ts ADDED
@@ -0,0 +1,52 @@
1
+ import type { StreamResponse } from "./response.js";
2
+ import type { RequestOptions } from "./session.js";
3
+ export interface SSEOptions extends RequestOptions {
4
+ /** Separate limits for a raw line and accumulated UTF-8 data; defaults to 1 MiB. */
5
+ maxEventBytes?: number;
6
+ /** Sets Last-Event-ID and the parser's initial ID. Reconnection is manual. */
7
+ lastEventId?: string;
8
+ }
9
+ export declare class ServerSentEvent {
10
+ readonly data: string;
11
+ readonly event: string;
12
+ readonly id: string;
13
+ readonly retry?: number | undefined;
14
+ constructor(data: string, event?: string, id?: string, retry?: number | undefined);
15
+ json<T = unknown>(): T;
16
+ }
17
+ export declare function validateSSEOptions(maximum: number, initialId?: string): void;
18
+ /** Internal pull parser. feed() pauses at each event, including within one chunk. */
19
+ export declare class SSEDecoder {
20
+ private readonly maximum;
21
+ lastEventId: string;
22
+ retry?: number;
23
+ private id;
24
+ private blockRetry?;
25
+ private line;
26
+ private length;
27
+ private data;
28
+ private size;
29
+ private event;
30
+ private first;
31
+ private skipLF;
32
+ constructor(maximum: number, initialId?: string);
33
+ private append;
34
+ private processLine;
35
+ feed(chunk: Uint8Array): Generator<ServerSentEvent>;
36
+ finish(): void;
37
+ }
38
+ export declare class SSEStream implements AsyncIterable<ServerSentEvent> {
39
+ readonly response: StreamResponse;
40
+ private readonly decoder;
41
+ private started;
42
+ private closed;
43
+ constructor(response: StreamResponse, maximum?: number, initialId?: string);
44
+ get lastEventId(): string;
45
+ get retry(): number | undefined;
46
+ private checkOpen;
47
+ [Symbol.asyncIterator](): AsyncGenerator<ServerSentEvent>;
48
+ private events;
49
+ cancel(): void;
50
+ close(): void;
51
+ closeAsync(): Promise<void>;
52
+ }