@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,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Cookies = void 0;
4
+ const tough_cookie_1 = require("tough-cookie");
5
+ const errors_js_1 = require("./errors.js");
6
+ class Cookies {
7
+ ensureOpen;
8
+ enabled;
9
+ jar = new tough_cookie_1.CookieJar();
10
+ constructor(ensureOpen, enabled = true) {
11
+ this.ensureOpen = ensureOpen;
12
+ this.enabled = enabled;
13
+ }
14
+ url(value) {
15
+ try {
16
+ const url = new URL(value.replace(/^ws:/, "http:").replace(/^wss:/, "https:"));
17
+ if (!["http:", "https:"].includes(url.protocol) || url.username || url.password)
18
+ throw new Error();
19
+ return url.href;
20
+ }
21
+ catch {
22
+ throw new errors_js_1.FastlsError("cookie URL must be absolute HTTP(S) without credentials");
23
+ }
24
+ }
25
+ getAll(url) {
26
+ this.ensureOpen();
27
+ const target = this.url(url);
28
+ return this.enabled ? this.jar.getCookiesSync(target).map(cookie => ({ name: cookie.key, value: cookie.value })) : [];
29
+ }
30
+ get(name, url) {
31
+ return this.getAll(url).find(cookie => cookie.name === name)?.value;
32
+ }
33
+ set(name, value, options) {
34
+ this.ensureOpen();
35
+ if (!this.enabled)
36
+ throw new errors_js_1.FastlsError("cookie store is disabled");
37
+ if (!/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(name) || /[^\x20-\x7e]|[;"\\]/.test(value) || Buffer.byteLength(name + value) > 4096)
38
+ throw new errors_js_1.FastlsError("invalid cookie name or value");
39
+ if (options.path !== undefined && (!options.path.startsWith("/") || /[\x00-\x1f;]/.test(options.path)))
40
+ throw new errors_js_1.FastlsError("invalid cookie path");
41
+ if (options.maxAge !== undefined && (!Number.isInteger(options.maxAge) || options.maxAge < -2147483648 || options.maxAge > 2147483647))
42
+ throw new errors_js_1.FastlsError("invalid cookie maxAge");
43
+ const expires = options.expires === undefined ? undefined : new Date(options.expires);
44
+ if (expires && (!Number.isFinite(expires.getTime()) || !/^\d{4}-\d\d-\d\dT.*(?:Z|[+-]\d\d:\d\d)$/.test(options.expires)))
45
+ throw new errors_js_1.FastlsError("expires must use RFC3339 format");
46
+ const cookie = new tough_cookie_1.Cookie({ key: name, value, path: options.path, domain: options.domain?.replace(/^\./, ""), secure: options.secure, httpOnly: options.httpOnly, maxAge: options.maxAge, expires });
47
+ try {
48
+ this.jar.setCookieSync(cookie, this.url(options.url));
49
+ }
50
+ catch {
51
+ throw new errors_js_1.FastlsError("invalid cookie domain or attributes");
52
+ }
53
+ }
54
+ delete(name, options) {
55
+ this.set(name, "", { ...options, maxAge: 0 });
56
+ }
57
+ clear() { this.ensureOpen(); this.jar.removeAllCookiesSync(); }
58
+ /** @internal */
59
+ header(url) { return this.getAll(url).map(cookie => `${cookie.name}=${cookie.value}`).join("; "); }
60
+ /** @internal */
61
+ extract(url, fields) {
62
+ this.ensureOpen();
63
+ if (!this.enabled)
64
+ return;
65
+ const target = this.url(url);
66
+ for (const field of fields)
67
+ this.jar.setCookieSync(field, target, { ignoreError: true });
68
+ }
69
+ }
70
+ exports.Cookies = Cookies;
@@ -0,0 +1,21 @@
1
+ import { StreamResponse, Transfer } from "./response.js";
2
+ /** A manual HTTP/2 exchange. At most one send/finish operation may be pending. */
3
+ export declare class DuplexExchange {
4
+ private readonly cancelPending?;
5
+ private responsePromise?;
6
+ private writing;
7
+ private finished;
8
+ private uploadStopped;
9
+ private finish;
10
+ private readonly upload;
11
+ private readonly transferPromise;
12
+ private transfer?;
13
+ private closed;
14
+ constructor(transfer: Transfer | Promise<Transfer>, cancelPending?: (() => void) | undefined);
15
+ get response(): Promise<StreamResponse>;
16
+ private write;
17
+ send(bytes: Uint8Array): Promise<void>;
18
+ finishUpload(): Promise<void>;
19
+ close(): void;
20
+ closeAsync(): Promise<void>;
21
+ }
package/dist/duplex.js ADDED
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DuplexExchange = void 0;
4
+ const errors_js_1 = require("./errors.js");
5
+ const response_js_1 = require("./response.js");
6
+ /** A manual HTTP/2 exchange. At most one send/finish operation may be pending. */
7
+ class DuplexExchange {
8
+ cancelPending;
9
+ responsePromise;
10
+ writing = false;
11
+ finished = false;
12
+ uploadStopped = false;
13
+ finish;
14
+ upload;
15
+ transferPromise;
16
+ transfer;
17
+ closed = false;
18
+ constructor(transfer, cancelPending) {
19
+ this.cancelPending = cancelPending;
20
+ const done = new Promise(resolve => { this.finish = resolve; });
21
+ this.transferPromise = Promise.resolve(transfer).then(value => {
22
+ this.transfer = value;
23
+ if (this.closed)
24
+ value.close();
25
+ const stopped = () => this.finish();
26
+ value.signal.addEventListener("abort", stopped, { once: true });
27
+ if (value.signal.aborted)
28
+ stopped();
29
+ void done.then(() => value.signal.removeEventListener("abort", stopped));
30
+ return value;
31
+ });
32
+ void this.transferPromise.catch(() => this.finish());
33
+ this.upload = { done, cleanup: Promise.resolve(), complete: () => this.finished,
34
+ check: () => this.transfer?.throwFailure() };
35
+ }
36
+ get response() {
37
+ return this.responsePromise ??= this.transferPromise.then(async (transfer) => {
38
+ if (this.closed)
39
+ throw new errors_js_1.FastlsError("exchange is closed");
40
+ const info = await transfer.headers();
41
+ return new response_js_1.StreamResponse(transfer, info, this.upload);
42
+ }).then(response => response, error => { this.close(); throw error; });
43
+ }
44
+ async write(bytes, end) {
45
+ if (this.writing)
46
+ throw new errors_js_1.FastlsError("another upload write is pending; await send() before sending again");
47
+ if (this.finished || this.uploadStopped)
48
+ throw new errors_js_1.FastlsError("upload is closed");
49
+ this.writing = true;
50
+ const accepted = (code) => {
51
+ if (code === 0)
52
+ return;
53
+ // The peer can stop receiving while its response remains readable.
54
+ this.uploadStopped = true;
55
+ this.finish();
56
+ throw new errors_js_1.FastlsError("server closed the upload");
57
+ };
58
+ try {
59
+ const transfer = await this.transferPromise;
60
+ if (this.closed)
61
+ throw new errors_js_1.FastlsError("exchange is closed");
62
+ transfer.ensureOpen();
63
+ if (end) {
64
+ accepted(await transfer.write(Buffer.alloc(0), true, transfer.signal));
65
+ this.finished = true;
66
+ this.finish();
67
+ }
68
+ else {
69
+ for (let offset = 0; offset < bytes.byteLength; offset += 65536) {
70
+ accepted(await transfer.write(bytes.subarray(offset, offset + 65536), false, transfer.signal));
71
+ }
72
+ }
73
+ }
74
+ catch (error) {
75
+ if (!this.uploadStopped)
76
+ this.transfer?.close(error instanceof Error ? error : new Error(String(error)));
77
+ throw error;
78
+ }
79
+ finally {
80
+ this.writing = false;
81
+ }
82
+ }
83
+ send(bytes) {
84
+ if (!(bytes instanceof Uint8Array))
85
+ return Promise.reject(new TypeError("send() requires Uint8Array or Buffer"));
86
+ return this.write(bytes, false);
87
+ }
88
+ finishUpload() {
89
+ return this.finished ? Promise.resolve() : this.write(Buffer.alloc(0), true);
90
+ }
91
+ close() { this.closed = true; this.transfer?.close(); this.cancelPending?.(); }
92
+ async closeAsync() { this.close(); await this.transferPromise.then(transfer => transfer.close(), () => { }); }
93
+ }
94
+ exports.DuplexExchange = DuplexExchange;
@@ -0,0 +1,28 @@
1
+ export declare class FastlsError extends Error {
2
+ name: string;
3
+ }
4
+ export declare class FastlsHTTPError extends FastlsError {
5
+ readonly response: import("./response.js").Response | import("./response.js").StreamResponse;
6
+ name: string;
7
+ readonly status: number;
8
+ constructor(response: import("./response.js").Response | import("./response.js").StreamResponse);
9
+ }
10
+ export type TimeoutPhase = "connect" | "write" | "headers" | "read" | "total" | "handshake";
11
+ export declare class FastlsTimeoutError extends FastlsError {
12
+ readonly phase: TimeoutPhase;
13
+ name: string;
14
+ constructor(message: string, phase: TimeoutPhase);
15
+ }
16
+ export declare class FastlsDecodeError extends FastlsError {
17
+ name: string;
18
+ }
19
+ export declare function responseError(info: {
20
+ error?: string;
21
+ error_code?: string;
22
+ timeout_phase?: string;
23
+ }): FastlsError;
24
+ export declare class AbortError extends FastlsError {
25
+ name: string;
26
+ readonly code = "ABORT_ERR";
27
+ constructor(message?: string, options?: ErrorOptions);
28
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AbortError = exports.FastlsDecodeError = exports.FastlsTimeoutError = exports.FastlsHTTPError = exports.FastlsError = void 0;
4
+ exports.responseError = responseError;
5
+ class FastlsError extends Error {
6
+ name = "FastlsError";
7
+ }
8
+ exports.FastlsError = FastlsError;
9
+ class FastlsHTTPError extends FastlsError {
10
+ response;
11
+ name = "FastlsHTTPError";
12
+ status;
13
+ constructor(response) {
14
+ super(`HTTP ${response.status}`);
15
+ this.response = response;
16
+ this.status = response.status;
17
+ }
18
+ }
19
+ exports.FastlsHTTPError = FastlsHTTPError;
20
+ class FastlsTimeoutError extends FastlsError {
21
+ phase;
22
+ name = "FastlsTimeoutError";
23
+ constructor(message, phase) {
24
+ super(message);
25
+ this.phase = phase;
26
+ }
27
+ }
28
+ exports.FastlsTimeoutError = FastlsTimeoutError;
29
+ class FastlsDecodeError extends FastlsError {
30
+ name = "FastlsDecodeError";
31
+ }
32
+ exports.FastlsDecodeError = FastlsDecodeError;
33
+ function responseError(info) {
34
+ const message = info.error ?? "native request failed";
35
+ if (info.error_code === "timeout")
36
+ return new FastlsTimeoutError(message, (info.timeout_phase ?? "total"));
37
+ if (info.error_code === "decode_error")
38
+ return new FastlsDecodeError(message);
39
+ return new FastlsError(message);
40
+ }
41
+ class AbortError extends FastlsError {
42
+ name = "AbortError";
43
+ code = "ABORT_ERR";
44
+ constructor(message = "The request was aborted", options) {
45
+ super(message, options);
46
+ }
47
+ }
48
+ exports.AbortError = AbortError;
@@ -0,0 +1,20 @@
1
+ import { Session, type RequestOptions } from "./session.js";
2
+ export interface FetchOptions extends RequestInit {
3
+ duplex?: "half";
4
+ profile?: string;
5
+ proxy?: string | null;
6
+ proxyCaFile?: string | null;
7
+ caFile?: string;
8
+ library?: string;
9
+ timeoutMs?: RequestOptions["timeoutMs"];
10
+ maxRedirects?: number;
11
+ }
12
+ /** A standard Node Response with Fastls transport metadata. */
13
+ export interface FetchResponse extends globalThis.Response {
14
+ readonly match: Readonly<Record<string, unknown>>;
15
+ readonly httpVersion: string;
16
+ }
17
+ /** @internal Used by Session.fetch; ownership is explicit to avoid closing shared pools. */
18
+ export declare function fetchWithSession(session: Session, input: string | URL | Request, init?: FetchOptions, owned?: boolean): Promise<FetchResponse>;
19
+ /** Fetch-compatible entry point. No persistent cookie jar is created. */
20
+ export declare function fetch(input: string | URL | Request, init?: FetchOptions): Promise<FetchResponse>;
package/dist/fetch.js ADDED
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fetchWithSession = fetchWithSession;
4
+ exports.fetch = fetch;
5
+ const node_stream_1 = require("node:stream");
6
+ const session_js_1 = require("./session.js");
7
+ /** @internal Used by Session.fetch; ownership is explicit to avoid closing shared pools. */
8
+ async function fetchWithSession(session, input, init = {}, owned = false) {
9
+ try {
10
+ for (const key of ["cache", "credentials", "integrity", "keepalive", "mode", "referrer", "referrerPolicy"]) {
11
+ if (init[key] !== undefined)
12
+ throw new TypeError(`Fastls fetch does not support ${key}`);
13
+ }
14
+ const allowed = new Set(["method", "headers", "body", "redirect", "signal", "duplex", "profile", "proxy", "proxyCaFile", "caFile", "library", "timeoutMs", "maxRedirects"]);
15
+ for (const key of Object.keys(init))
16
+ if (!allowed.has(key))
17
+ throw new TypeError(`Fastls fetch does not support ${key}`);
18
+ // Request performs the platform's normal URL/method/header validation and
19
+ // serializes URLSearchParams, Blob and FormData without a second encoder.
20
+ const request = new Request(input, init);
21
+ const headers = Object.fromEntries(request.headers);
22
+ let body;
23
+ if (request.body) {
24
+ if (typeof init.body === "string" || init.body instanceof Uint8Array)
25
+ body = init.body;
26
+ else if (init.body instanceof URLSearchParams)
27
+ body = init.body.toString();
28
+ else
29
+ body = node_stream_1.Readable.fromWeb(request.body);
30
+ }
31
+ const stream = await session.stream(request.method, request.url, {
32
+ headers, body, profile: init.profile, proxy: init.proxy, proxyCaFile: init.proxyCaFile,
33
+ timeoutMs: init.timeoutMs, maxRedirects: init.maxRedirects,
34
+ allowRedirects: request.redirect === "follow", signal: request.signal,
35
+ });
36
+ let closing;
37
+ const close = () => closing ??= (async () => {
38
+ stream.closedSignal.removeEventListener("abort", abort);
39
+ try {
40
+ await stream.closeAsync();
41
+ }
42
+ finally {
43
+ if (owned)
44
+ await session.closeAsync();
45
+ }
46
+ })();
47
+ const abort = () => { void close().catch(() => { }); };
48
+ stream.closedSignal.addEventListener("abort", abort, { once: true });
49
+ if (stream.closedSignal.aborted) {
50
+ await close();
51
+ throw stream.closedSignal.reason;
52
+ }
53
+ try {
54
+ if (request.redirect === "error" && [301, 302, 303, 307, 308].includes(stream.status))
55
+ throw new TypeError("redirect encountered with redirect: error");
56
+ const responseHeaders = new globalThis.Headers();
57
+ for (const [name, value] of stream.headers.multiItems())
58
+ responseHeaders.append(name, value);
59
+ const empty = request.method === "HEAD" || [204, 205, 304].includes(stream.status);
60
+ const iterator = stream.body[Symbol.asyncIterator]();
61
+ const responseBody = empty ? null : new ReadableStream({
62
+ async pull(controller) {
63
+ try {
64
+ const item = await iterator.next();
65
+ if (item.done) {
66
+ await close();
67
+ controller.close();
68
+ }
69
+ else
70
+ controller.enqueue(item.value);
71
+ }
72
+ catch (error) {
73
+ try {
74
+ await close();
75
+ }
76
+ finally {
77
+ controller.error(error);
78
+ }
79
+ }
80
+ },
81
+ async cancel() { try {
82
+ await iterator.return?.();
83
+ }
84
+ finally {
85
+ await close();
86
+ } },
87
+ }, { highWaterMark: 0 });
88
+ const response = new globalThis.Response(responseBody, { status: stream.status, headers: responseHeaders });
89
+ Object.defineProperties(response, {
90
+ url: { value: stream.url }, redirected: { value: stream.url !== request.url },
91
+ match: { value: stream.match }, httpVersion: { value: stream.httpVersion },
92
+ });
93
+ if (empty)
94
+ await close();
95
+ return response;
96
+ }
97
+ catch (error) {
98
+ await close();
99
+ throw error;
100
+ }
101
+ }
102
+ catch (error) {
103
+ if (owned)
104
+ await session.closeAsync();
105
+ throw error;
106
+ }
107
+ }
108
+ /** Fetch-compatible entry point. No persistent cookie jar is created. */
109
+ async function fetch(input, init = {}) {
110
+ const options = { cookies: false, profile: init.profile, proxy: init.proxy,
111
+ proxyCaFile: init.proxyCaFile, caFile: init.caFile, library: init.library, timeoutMs: init.timeoutMs };
112
+ return fetchWithSession(new session_js_1.Session(options), input, init, true);
113
+ }
@@ -0,0 +1,12 @@
1
+ /** Read-only, case-insensitive response headers. Values are never comma-split. */
2
+ export declare class Headers implements Iterable<[string, string]> {
3
+ #private;
4
+ constructor(values?: Record<string, string[]>, fallback?: Record<string, string>);
5
+ get(name: string, fallback: string): string;
6
+ get(name: string): string | undefined;
7
+ getAll(name: string): string[];
8
+ has(name: string): boolean;
9
+ entries(): IterableIterator<[string, string]>;
10
+ multiItems(): IterableIterator<[string, string]>;
11
+ [Symbol.iterator](): IterableIterator<[string, string]>;
12
+ }
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Headers = void 0;
4
+ /** Read-only, case-insensitive response headers. Values are never comma-split. */
5
+ class Headers {
6
+ #values = new Map();
7
+ constructor(values = {}, fallback = {}) {
8
+ for (const [name, value] of Object.entries(fallback))
9
+ this.#values.set(name.toLowerCase(), [value]);
10
+ for (const [name, list] of Object.entries(values))
11
+ this.#values.set(name.toLowerCase(), [...list]);
12
+ }
13
+ get(name, fallback) { return this.#values.get(name.toLowerCase())?.[0] ?? fallback; }
14
+ getAll(name) { return [...(this.#values.get(name.toLowerCase()) ?? [])]; }
15
+ has(name) { return this.#values.has(name.toLowerCase()); }
16
+ *entries() {
17
+ for (const [name, values] of this.#values)
18
+ if (values.length)
19
+ yield [name, values[0]];
20
+ }
21
+ *multiItems() {
22
+ for (const [name, values] of this.#values)
23
+ for (const value of values)
24
+ yield [name, value];
25
+ }
26
+ [Symbol.iterator]() { return this.entries(); }
27
+ }
28
+ exports.Headers = Headers;
@@ -0,0 +1,18 @@
1
+ export { Session } from "./session.js";
2
+ export { fetch } from "./fetch.js";
3
+ export type { FetchOptions, FetchResponse } from "./fetch.js";
4
+ export type { SessionOptions, RequestOptions, DuplexOptions, Stats } from "./session.js";
5
+ export { DuplexExchange } from "./duplex.js";
6
+ export { WebSocket, WebSocketClosed } from "./websocket.js";
7
+ export type { WebSocketOptions } from "./websocket.js";
8
+ export { ServerSentEvent, SSEStream } from "./sse.js";
9
+ export type { SSEOptions } from "./sse.js";
10
+ export { Response, StreamResponse } from "./response.js";
11
+ export { Headers } from "./headers.js";
12
+ export { Cookies } from "./cookies.js";
13
+ export type { Cookie, CookieOptions } from "./cookies.js";
14
+ export { FastlsError, FastlsHTTPError, AbortError, FastlsDecodeError, FastlsTimeoutError } from "./errors.js";
15
+ export type { QueryScalar, QueryParams, FormFields, JSONValue, MultipartReader, MultipartBody, MultipartFile, MultipartFiles } from "./request-options.js";
16
+ export type { TimeoutPhase } from "./errors.js";
17
+ export type { TimeoutPolicy, Timeout } from "./timeout.js";
18
+ export type { UploadBody, ByteSource } from "./upload.js";
package/dist/index.js ADDED
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FastlsTimeoutError = exports.FastlsDecodeError = exports.AbortError = exports.FastlsHTTPError = exports.FastlsError = exports.Cookies = exports.Headers = exports.StreamResponse = exports.Response = exports.SSEStream = exports.ServerSentEvent = exports.WebSocketClosed = exports.WebSocket = exports.DuplexExchange = exports.Session = void 0;
4
+ var session_js_1 = require("./session.js");
5
+ Object.defineProperty(exports, "Session", { enumerable: true, get: function () { return session_js_1.Session; } });
6
+ var fetch_js_1 = require("./fetch.js");
7
+ Object.defineProperty(exports, "fetch", { enumerable: true, get: function () { return fetch_js_1.fetch; } });
8
+ var duplex_js_1 = require("./duplex.js");
9
+ Object.defineProperty(exports, "DuplexExchange", { enumerable: true, get: function () { return duplex_js_1.DuplexExchange; } });
10
+ var websocket_js_1 = require("./websocket.js");
11
+ Object.defineProperty(exports, "WebSocket", { enumerable: true, get: function () { return websocket_js_1.WebSocket; } });
12
+ Object.defineProperty(exports, "WebSocketClosed", { enumerable: true, get: function () { return websocket_js_1.WebSocketClosed; } });
13
+ var sse_js_1 = require("./sse.js");
14
+ Object.defineProperty(exports, "ServerSentEvent", { enumerable: true, get: function () { return sse_js_1.ServerSentEvent; } });
15
+ Object.defineProperty(exports, "SSEStream", { enumerable: true, get: function () { return sse_js_1.SSEStream; } });
16
+ var response_js_1 = require("./response.js");
17
+ Object.defineProperty(exports, "Response", { enumerable: true, get: function () { return response_js_1.Response; } });
18
+ Object.defineProperty(exports, "StreamResponse", { enumerable: true, get: function () { return response_js_1.StreamResponse; } });
19
+ var headers_js_1 = require("./headers.js");
20
+ Object.defineProperty(exports, "Headers", { enumerable: true, get: function () { return headers_js_1.Headers; } });
21
+ var cookies_js_1 = require("./cookies.js");
22
+ Object.defineProperty(exports, "Cookies", { enumerable: true, get: function () { return cookies_js_1.Cookies; } });
23
+ var errors_js_1 = require("./errors.js");
24
+ Object.defineProperty(exports, "FastlsError", { enumerable: true, get: function () { return errors_js_1.FastlsError; } });
25
+ Object.defineProperty(exports, "FastlsHTTPError", { enumerable: true, get: function () { return errors_js_1.FastlsHTTPError; } });
26
+ Object.defineProperty(exports, "AbortError", { enumerable: true, get: function () { return errors_js_1.AbortError; } });
27
+ Object.defineProperty(exports, "FastlsDecodeError", { enumerable: true, get: function () { return errors_js_1.FastlsDecodeError; } });
28
+ Object.defineProperty(exports, "FastlsTimeoutError", { enumerable: true, get: function () { return errors_js_1.FastlsTimeoutError; } });
package/dist/index.mjs ADDED
@@ -0,0 +1,3 @@
1
+ import sdk from './index.js';
2
+ export const fetch = sdk.fetch;
3
+ export const { Session, Response, StreamResponse, DuplexExchange, WebSocket, WebSocketClosed, ServerSentEvent, SSEStream, Headers, Cookies, FastlsError, FastlsHTTPError, AbortError, FastlsDecodeError, FastlsTimeoutError } = sdk;
@@ -0,0 +1,2 @@
1
+ /** Explicit overrides take priority; a present candidate manifest must validate. */
2
+ export declare function libraryPath(library?: string, packageRoot?: string): string;
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.libraryPath = libraryPath;
4
+ const node_crypto_1 = require("node:crypto");
5
+ const node_fs_1 = require("node:fs");
6
+ const node_path_1 = require("node:path");
7
+ const node_module_1 = require("node:module");
8
+ const errors_js_1 = require("./errors.js");
9
+ const verified = new Set();
10
+ let glibcRuntime;
11
+ let glibcChecked = false;
12
+ /** Explicit overrides take priority; a present candidate manifest must validate. */
13
+ function libraryPath(library, packageRoot = (0, node_path_1.resolve)(__dirname, "..")) {
14
+ const override = library ?? process.env.FASTLS_LIBRARY;
15
+ const name = process.platform === "win32" ? "fastls.dll"
16
+ : process.platform === "darwin" ? "libfastls.dylib" : "libfastls.so";
17
+ try {
18
+ if (override !== undefined)
19
+ return (0, node_fs_1.realpathSync)((0, node_path_1.resolve)(override));
20
+ let nativeRoot = packageRoot;
21
+ let manifestPath = (0, node_path_1.resolve)(nativeRoot, "native/manifest.json");
22
+ if (!(0, node_fs_1.existsSync)(manifestPath) && !(0, node_fs_1.existsSync)((0, node_path_1.resolve)(packageRoot, "src/native.ts"))) {
23
+ const suffix = `${process.platform}-${process.arch}${process.platform === "linux" ? "-glibc" : ""}`;
24
+ const require = (0, node_module_1.createRequire)((0, node_path_1.resolve)(packageRoot, "package.json"));
25
+ let metadataPath;
26
+ try {
27
+ metadataPath = require.resolve(`@fastls/fastls-${suffix}/package.json`);
28
+ }
29
+ catch (cause) {
30
+ throw new Error(`no native manifest or optional package for ${suffix}; reinstall with optional dependencies enabled or supply an external library`, { cause });
31
+ }
32
+ const installed = JSON.parse((0, node_fs_1.readFileSync)(metadataPath, "utf8"));
33
+ const wrapper = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.resolve)(packageRoot, "package.json"), "utf8"));
34
+ if (installed.name !== `@fastls/fastls-${suffix}` || installed.version !== wrapper.version) {
35
+ throw new Error("native package version mismatch; reinstall matching Fastls packages");
36
+ }
37
+ nativeRoot = (0, node_path_1.resolve)(metadataPath, "..");
38
+ manifestPath = (0, node_path_1.resolve)(nativeRoot, "native/manifest.json");
39
+ }
40
+ if (!(0, node_fs_1.existsSync)(manifestPath)) {
41
+ if (!(0, node_fs_1.existsSync)((0, node_path_1.resolve)(packageRoot, "src/native.ts")))
42
+ throw new Error("installed package has no native manifest; supply an external library");
43
+ return (0, node_fs_1.realpathSync)((0, node_path_1.resolve)(packageRoot, "../dist", name));
44
+ }
45
+ const manifest = JSON.parse((0, node_fs_1.readFileSync)(manifestPath, "utf8"));
46
+ if (manifest.schema !== 1 || manifest.abi !== 1 || manifest.platform !== process.platform || manifest.arch !== process.arch) {
47
+ throw new Error(`candidate platform mismatch: expected ${process.platform}/${process.arch}, received ${manifest.platform}/${manifest.arch}`);
48
+ }
49
+ if (manifest.file !== name || !/^[a-f0-9]{64}$/.test(manifest.sha256))
50
+ throw new Error("invalid candidate library manifest");
51
+ if (process.platform === "linux") {
52
+ if (!glibcChecked) {
53
+ const report = process.report.getReport();
54
+ glibcRuntime = report.header?.glibcVersionRuntime;
55
+ glibcChecked = true;
56
+ }
57
+ const current = glibcRuntime;
58
+ const minimum = manifest.requirements?.glibc;
59
+ if (!current || !/^\d+\.\d+$/.test(minimum))
60
+ throw new Error("this candidate requires Linux glibc; musl is not supported");
61
+ const [major, minor] = current.split(".").map(Number), [minMajor, minMinor] = minimum.split(".").map(Number);
62
+ if (major < minMajor || (major === minMajor && minor < minMinor))
63
+ throw new Error(`candidate requires glibc >= ${minimum}; found ${current}`);
64
+ }
65
+ const path = (0, node_fs_1.realpathSync)((0, node_path_1.resolve)(nativeRoot, "native", name)), stat = (0, node_fs_1.statSync)(path);
66
+ const key = `${path}:${stat.size}:${stat.mtimeMs}:${stat.ctimeMs}:${manifest.sha256}`;
67
+ if (!verified.has(key)) {
68
+ if ((0, node_crypto_1.createHash)("sha256").update((0, node_fs_1.readFileSync)(path)).digest("hex") !== manifest.sha256)
69
+ throw new Error("candidate library SHA-256 mismatch");
70
+ verified.add(key);
71
+ }
72
+ return path;
73
+ }
74
+ catch (cause) {
75
+ throw new errors_js_1.FastlsError("Cannot find Fastls library or validate native candidate; set FASTLS_LIBRARY or pass { library: absolutePath }", { cause });
76
+ }
77
+ }
@@ -0,0 +1,54 @@
1
+ export declare function parseNativeJSON(text: string): unknown;
2
+ export declare function encodeNativeJSON(value: unknown, limit?: number): Buffer;
3
+ export declare function asHandle(value: unknown): bigint;
4
+ export interface NativeInfo {
5
+ state: "pending" | "headers" | "eof" | "error";
6
+ status?: number;
7
+ headers?: Record<string, string>;
8
+ header_values?: Record<string, string[]>;
9
+ match: Readonly<Record<string, unknown>>;
10
+ error?: string;
11
+ error_code?: string;
12
+ timeout_phase?: string;
13
+ decoded?: boolean;
14
+ protocol?: string;
15
+ final_url?: string;
16
+ trailer_values?: Record<string, string[]>;
17
+ wire_header_values?: Record<string, string[]>;
18
+ upload_discarded?: boolean;
19
+ websocket?: {
20
+ subprotocol: string;
21
+ write_pending: boolean;
22
+ close_code: number;
23
+ close_reason: string;
24
+ close_received: boolean;
25
+ pings: number;
26
+ pongs: number;
27
+ last_pong_base64: string;
28
+ max_message_bytes: number;
29
+ alpn_policy: string;
30
+ };
31
+ }
32
+ export declare class Native {
33
+ private functions;
34
+ readonly notificationsAvailable: boolean;
35
+ readonly websocketAvailable: boolean;
36
+ readonly path: string;
37
+ static load(library?: string): Native;
38
+ private constructor();
39
+ call<T = unknown>(name: string, ...args: unknown[]): T;
40
+ start(input: unknown): bigint;
41
+ wsStart(input: unknown): bigint;
42
+ wsSend(handle: bigint, opcode: number, data: Uint8Array): number;
43
+ wsRead(handle: bigint, buffer: Buffer): {
44
+ code: number;
45
+ length: number;
46
+ opcode: number;
47
+ end: boolean;
48
+ };
49
+ read(handle: bigint, buffer: Buffer): {
50
+ code: number;
51
+ length: number;
52
+ };
53
+ write(handle: bigint, buffer: Uint8Array, end?: boolean): number;
54
+ }