@zero-transfer/core 0.1.6 → 0.4.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.
package/README.md CHANGED
@@ -20,7 +20,7 @@ import { createLocalProviderFactory } from "@zero-transfer/core";
20
20
 
21
21
  ## Public surface
22
22
 
23
- This package narrows [`@zero-transfer/sdk`](https://www.npmjs.com/package/@zero-transfer/sdk) to **92** exports. Every symbol is re-exported from the SDK; the table below links into the full API reference:
23
+ This package publishes a narrowed surface of **92** exports. These symbols are also available from [`@zero-transfer/sdk`](https://www.npmjs.com/package/@zero-transfer/sdk); the table below links into the full API reference:
24
24
 
25
25
  | Symbol | Kind | Notes |
26
26
  | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ------------------ |
package/dist/index.cjs CHANGED
@@ -63,6 +63,7 @@ __export(core_exports, {
63
63
  createLocalProviderFactory: () => createLocalProviderFactory,
64
64
  createMemoryProviderFactory: () => createMemoryProviderFactory,
65
65
  createOAuthTokenSecretSource: () => createOAuthTokenSecretSource,
66
+ createPooledTransferClient: () => createPooledTransferClient,
66
67
  createProgressEvent: () => createProgressEvent,
67
68
  createProviderTransferExecutor: () => createProviderTransferExecutor,
68
69
  createRemoteBrowser: () => createRemoteBrowser,
@@ -553,7 +554,7 @@ function createPinnedHostKeyError(value) {
553
554
  function createSshAlgorithmsError(value) {
554
555
  return new ConfigurationError({
555
556
  details: { algorithms: value },
556
- message: "Connection profile ssh.algorithms must use ssh2-compatible non-empty algorithm lists",
557
+ message: "Connection profile ssh.algorithms must use SSH-compatible non-empty algorithm lists",
557
558
  retryable: false
558
559
  });
559
560
  }
@@ -1881,6 +1882,186 @@ function summarizeDiagnosticError(error) {
1881
1882
  return { message: String(error) };
1882
1883
  }
1883
1884
 
1885
+ // src/core/ConnectionPool.ts
1886
+ var DEFAULT_MAX_IDLE_PER_KEY = 4;
1887
+ var DEFAULT_IDLE_TIMEOUT_MS = 6e4;
1888
+ function createPooledTransferClient(inner, options = {}) {
1889
+ const maxIdlePerKey = Math.max(1, options.maxIdlePerKey ?? DEFAULT_MAX_IDLE_PER_KEY);
1890
+ const idleTimeoutMs = Math.max(0, options.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS);
1891
+ const keyOf = options.keyOf ?? defaultKeyOf;
1892
+ const state = {
1893
+ drained: false,
1894
+ idle: /* @__PURE__ */ new Map()
1895
+ };
1896
+ const release = (key, session, tainted) => {
1897
+ if (tainted || state.drained) {
1898
+ return safelyDisconnect(session);
1899
+ }
1900
+ let bucket = state.idle.get(key);
1901
+ if (bucket === void 0) {
1902
+ bucket = [];
1903
+ state.idle.set(key, bucket);
1904
+ }
1905
+ const entry = { session };
1906
+ if (idleTimeoutMs > 0) {
1907
+ entry.idleTimer = setTimeout(() => {
1908
+ evictEntry(state, key, entry);
1909
+ }, idleTimeoutMs);
1910
+ const timer = entry.idleTimer;
1911
+ if (timer !== void 0 && typeof timer.unref === "function") {
1912
+ timer.unref();
1913
+ }
1914
+ }
1915
+ bucket.push(entry);
1916
+ while (bucket.length > maxIdlePerKey) {
1917
+ const dropped = bucket.shift();
1918
+ if (dropped !== void 0) {
1919
+ clearEntryTimer(dropped);
1920
+ void safelyDisconnect(dropped.session);
1921
+ }
1922
+ }
1923
+ return Promise.resolve();
1924
+ };
1925
+ const acquire = async (profile) => {
1926
+ const key = keyOf(profile);
1927
+ const bucket = state.idle.get(key);
1928
+ if (bucket !== void 0 && bucket.length > 0) {
1929
+ const entry = bucket.pop();
1930
+ if (entry !== void 0) {
1931
+ clearEntryTimer(entry);
1932
+ if (bucket.length === 0) state.idle.delete(key);
1933
+ return { key, session: entry.session };
1934
+ }
1935
+ }
1936
+ const session = await inner.connect(profile);
1937
+ return { key, session };
1938
+ };
1939
+ return {
1940
+ connect: async (profile) => {
1941
+ const { key, session } = await acquire(profile);
1942
+ return wrapPooledSession(session, key, release);
1943
+ },
1944
+ drainPool: async () => {
1945
+ state.drained = true;
1946
+ const entries = [];
1947
+ for (const bucket of state.idle.values()) {
1948
+ for (const entry of bucket) {
1949
+ clearEntryTimer(entry);
1950
+ entries.push(entry);
1951
+ }
1952
+ }
1953
+ state.idle.clear();
1954
+ await Promise.all(entries.map((entry) => safelyDisconnect(entry.session)));
1955
+ },
1956
+ getCapabilities: ((providerId) => {
1957
+ if (providerId === void 0) return inner.getCapabilities();
1958
+ return inner.getCapabilities(providerId);
1959
+ }),
1960
+ hasProvider: (providerId) => inner.hasProvider(providerId),
1961
+ poolSize: () => {
1962
+ let total = 0;
1963
+ for (const bucket of state.idle.values()) total += bucket.length;
1964
+ return total;
1965
+ }
1966
+ };
1967
+ }
1968
+ function defaultKeyOf(profile) {
1969
+ const provider = profile.provider ?? profile.protocol ?? "unknown";
1970
+ const host = profile.host ?? "";
1971
+ const port = profile.port ?? "";
1972
+ const username = typeof profile.username === "string" ? profile.username : "";
1973
+ return `${provider}|${host}|${String(port)}|${username}`;
1974
+ }
1975
+ function evictEntry(state, key, entry) {
1976
+ const bucket = state.idle.get(key);
1977
+ if (bucket === void 0) return;
1978
+ const index = bucket.indexOf(entry);
1979
+ if (index < 0) return;
1980
+ bucket.splice(index, 1);
1981
+ if (bucket.length === 0) state.idle.delete(key);
1982
+ clearEntryTimer(entry);
1983
+ void safelyDisconnect(entry.session);
1984
+ }
1985
+ function clearEntryTimer(entry) {
1986
+ if (entry.idleTimer !== void 0) {
1987
+ clearTimeout(entry.idleTimer);
1988
+ delete entry.idleTimer;
1989
+ }
1990
+ }
1991
+ async function safelyDisconnect(session) {
1992
+ try {
1993
+ await session.disconnect();
1994
+ } catch {
1995
+ }
1996
+ }
1997
+ function isTaintingError(error) {
1998
+ return error instanceof ConnectionError || error instanceof TimeoutError || error instanceof ProtocolError;
1999
+ }
2000
+ function wrapPooledSession(session, key, release) {
2001
+ let tainted = false;
2002
+ let released = false;
2003
+ const guard = (fn) => {
2004
+ let promise;
2005
+ try {
2006
+ promise = fn();
2007
+ } catch (error) {
2008
+ if (isTaintingError(error)) tainted = true;
2009
+ return Promise.reject(error instanceof Error ? error : new Error(String(error)));
2010
+ }
2011
+ return promise.catch((error) => {
2012
+ if (isTaintingError(error)) tainted = true;
2013
+ throw error;
2014
+ });
2015
+ };
2016
+ const fs = wrapFs(session.fs, guard);
2017
+ const transfers = session.transfers === void 0 ? void 0 : wrapTransfers(session.transfers, guard);
2018
+ const wrapped = {
2019
+ capabilities: session.capabilities,
2020
+ disconnect: async () => {
2021
+ if (released) return;
2022
+ released = true;
2023
+ await release(key, session, tainted);
2024
+ },
2025
+ fs,
2026
+ provider: session.provider,
2027
+ ...transfers !== void 0 ? { transfers } : {}
2028
+ };
2029
+ if (typeof session.raw === "function") {
2030
+ const rawFn = session.raw.bind(session);
2031
+ wrapped.raw = () => rawFn();
2032
+ }
2033
+ return wrapped;
2034
+ }
2035
+ function wrapFs(fs, guard) {
2036
+ const wrapped = {
2037
+ list: (path2, options) => guard(() => options !== void 0 ? fs.list(path2, options) : fs.list(path2)),
2038
+ stat: (path2, options) => guard(() => options !== void 0 ? fs.stat(path2, options) : fs.stat(path2))
2039
+ };
2040
+ if (typeof fs.remove === "function") {
2041
+ const remove = fs.remove.bind(fs);
2042
+ wrapped.remove = (path2, options) => guard(() => options !== void 0 ? remove(path2, options) : remove(path2));
2043
+ }
2044
+ if (typeof fs.rename === "function") {
2045
+ const rename2 = fs.rename.bind(fs);
2046
+ wrapped.rename = (from, to, options) => guard(() => options !== void 0 ? rename2(from, to, options) : rename2(from, to));
2047
+ }
2048
+ if (typeof fs.mkdir === "function") {
2049
+ const mkdir2 = fs.mkdir.bind(fs);
2050
+ wrapped.mkdir = (path2, options) => guard(() => options !== void 0 ? mkdir2(path2, options) : mkdir2(path2));
2051
+ }
2052
+ if (typeof fs.rmdir === "function") {
2053
+ const rmdir = fs.rmdir.bind(fs);
2054
+ wrapped.rmdir = (path2, options) => guard(() => options !== void 0 ? rmdir(path2, options) : rmdir(path2));
2055
+ }
2056
+ return wrapped;
2057
+ }
2058
+ function wrapTransfers(transfers, guard) {
2059
+ return {
2060
+ read: (request) => guard(() => Promise.resolve(transfers.read(request))),
2061
+ write: (request) => guard(() => Promise.resolve(transfers.write(request)))
2062
+ };
2063
+ }
2064
+
1884
2065
  // src/providers/local/LocalProvider.ts
1885
2066
  var import_node_fs = require("fs");
1886
2067
  var import_promises2 = require("fs/promises");
@@ -4743,6 +4924,7 @@ function isModifiedAtDifferent2(source, destination, toleranceMs) {
4743
4924
  createLocalProviderFactory,
4744
4925
  createMemoryProviderFactory,
4745
4926
  createOAuthTokenSecretSource,
4927
+ createPooledTransferClient,
4746
4928
  createProgressEvent,
4747
4929
  createProviderTransferExecutor,
4748
4930
  createRemoteBrowser,