@zero-transfer/classic 0.3.1 → 0.4.2

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/dist/index.mjs CHANGED
@@ -1767,6 +1767,186 @@ function summarizeDiagnosticError(error) {
1767
1767
  return { message: String(error) };
1768
1768
  }
1769
1769
 
1770
+ // src/core/ConnectionPool.ts
1771
+ var DEFAULT_MAX_IDLE_PER_KEY = 4;
1772
+ var DEFAULT_IDLE_TIMEOUT_MS = 6e4;
1773
+ function createPooledTransferClient(inner, options = {}) {
1774
+ const maxIdlePerKey = Math.max(1, options.maxIdlePerKey ?? DEFAULT_MAX_IDLE_PER_KEY);
1775
+ const idleTimeoutMs = Math.max(0, options.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS);
1776
+ const keyOf = options.keyOf ?? defaultKeyOf;
1777
+ const state = {
1778
+ drained: false,
1779
+ idle: /* @__PURE__ */ new Map()
1780
+ };
1781
+ const release = (key, session, tainted) => {
1782
+ if (tainted || state.drained) {
1783
+ return safelyDisconnect(session);
1784
+ }
1785
+ let bucket = state.idle.get(key);
1786
+ if (bucket === void 0) {
1787
+ bucket = [];
1788
+ state.idle.set(key, bucket);
1789
+ }
1790
+ const entry = { session };
1791
+ if (idleTimeoutMs > 0) {
1792
+ entry.idleTimer = setTimeout(() => {
1793
+ evictEntry(state, key, entry);
1794
+ }, idleTimeoutMs);
1795
+ const timer = entry.idleTimer;
1796
+ if (timer !== void 0 && typeof timer.unref === "function") {
1797
+ timer.unref();
1798
+ }
1799
+ }
1800
+ bucket.push(entry);
1801
+ while (bucket.length > maxIdlePerKey) {
1802
+ const dropped = bucket.shift();
1803
+ if (dropped !== void 0) {
1804
+ clearEntryTimer(dropped);
1805
+ void safelyDisconnect(dropped.session);
1806
+ }
1807
+ }
1808
+ return Promise.resolve();
1809
+ };
1810
+ const acquire = async (profile) => {
1811
+ const key = keyOf(profile);
1812
+ const bucket = state.idle.get(key);
1813
+ if (bucket !== void 0 && bucket.length > 0) {
1814
+ const entry = bucket.pop();
1815
+ if (entry !== void 0) {
1816
+ clearEntryTimer(entry);
1817
+ if (bucket.length === 0) state.idle.delete(key);
1818
+ return { key, session: entry.session };
1819
+ }
1820
+ }
1821
+ const session = await inner.connect(profile);
1822
+ return { key, session };
1823
+ };
1824
+ return {
1825
+ connect: async (profile) => {
1826
+ const { key, session } = await acquire(profile);
1827
+ return wrapPooledSession(session, key, release);
1828
+ },
1829
+ drainPool: async () => {
1830
+ state.drained = true;
1831
+ const entries = [];
1832
+ for (const bucket of state.idle.values()) {
1833
+ for (const entry of bucket) {
1834
+ clearEntryTimer(entry);
1835
+ entries.push(entry);
1836
+ }
1837
+ }
1838
+ state.idle.clear();
1839
+ await Promise.all(entries.map((entry) => safelyDisconnect(entry.session)));
1840
+ },
1841
+ getCapabilities: ((providerId) => {
1842
+ if (providerId === void 0) return inner.getCapabilities();
1843
+ return inner.getCapabilities(providerId);
1844
+ }),
1845
+ hasProvider: (providerId) => inner.hasProvider(providerId),
1846
+ poolSize: () => {
1847
+ let total = 0;
1848
+ for (const bucket of state.idle.values()) total += bucket.length;
1849
+ return total;
1850
+ }
1851
+ };
1852
+ }
1853
+ function defaultKeyOf(profile) {
1854
+ const provider = profile.provider ?? profile.protocol ?? "unknown";
1855
+ const host = profile.host ?? "";
1856
+ const port = profile.port ?? "";
1857
+ const username = typeof profile.username === "string" ? profile.username : "";
1858
+ return `${provider}|${host}|${String(port)}|${username}`;
1859
+ }
1860
+ function evictEntry(state, key, entry) {
1861
+ const bucket = state.idle.get(key);
1862
+ if (bucket === void 0) return;
1863
+ const index = bucket.indexOf(entry);
1864
+ if (index < 0) return;
1865
+ bucket.splice(index, 1);
1866
+ if (bucket.length === 0) state.idle.delete(key);
1867
+ clearEntryTimer(entry);
1868
+ void safelyDisconnect(entry.session);
1869
+ }
1870
+ function clearEntryTimer(entry) {
1871
+ if (entry.idleTimer !== void 0) {
1872
+ clearTimeout(entry.idleTimer);
1873
+ delete entry.idleTimer;
1874
+ }
1875
+ }
1876
+ async function safelyDisconnect(session) {
1877
+ try {
1878
+ await session.disconnect();
1879
+ } catch {
1880
+ }
1881
+ }
1882
+ function isTaintingError(error) {
1883
+ return error instanceof ConnectionError || error instanceof TimeoutError || error instanceof ProtocolError;
1884
+ }
1885
+ function wrapPooledSession(session, key, release) {
1886
+ let tainted = false;
1887
+ let released = false;
1888
+ const guard = (fn) => {
1889
+ let promise;
1890
+ try {
1891
+ promise = fn();
1892
+ } catch (error) {
1893
+ if (isTaintingError(error)) tainted = true;
1894
+ return Promise.reject(error instanceof Error ? error : new Error(String(error)));
1895
+ }
1896
+ return promise.catch((error) => {
1897
+ if (isTaintingError(error)) tainted = true;
1898
+ throw error;
1899
+ });
1900
+ };
1901
+ const fs = wrapFs(session.fs, guard);
1902
+ const transfers = session.transfers === void 0 ? void 0 : wrapTransfers(session.transfers, guard);
1903
+ const wrapped = {
1904
+ capabilities: session.capabilities,
1905
+ disconnect: async () => {
1906
+ if (released) return;
1907
+ released = true;
1908
+ await release(key, session, tainted);
1909
+ },
1910
+ fs,
1911
+ provider: session.provider,
1912
+ ...transfers !== void 0 ? { transfers } : {}
1913
+ };
1914
+ if (typeof session.raw === "function") {
1915
+ const rawFn = session.raw.bind(session);
1916
+ wrapped.raw = () => rawFn();
1917
+ }
1918
+ return wrapped;
1919
+ }
1920
+ function wrapFs(fs, guard) {
1921
+ const wrapped = {
1922
+ list: (path2, options) => guard(() => options !== void 0 ? fs.list(path2, options) : fs.list(path2)),
1923
+ stat: (path2, options) => guard(() => options !== void 0 ? fs.stat(path2, options) : fs.stat(path2))
1924
+ };
1925
+ if (typeof fs.remove === "function") {
1926
+ const remove = fs.remove.bind(fs);
1927
+ wrapped.remove = (path2, options) => guard(() => options !== void 0 ? remove(path2, options) : remove(path2));
1928
+ }
1929
+ if (typeof fs.rename === "function") {
1930
+ const rename2 = fs.rename.bind(fs);
1931
+ wrapped.rename = (from, to, options) => guard(() => options !== void 0 ? rename2(from, to, options) : rename2(from, to));
1932
+ }
1933
+ if (typeof fs.mkdir === "function") {
1934
+ const mkdir2 = fs.mkdir.bind(fs);
1935
+ wrapped.mkdir = (path2, options) => guard(() => options !== void 0 ? mkdir2(path2, options) : mkdir2(path2));
1936
+ }
1937
+ if (typeof fs.rmdir === "function") {
1938
+ const rmdir = fs.rmdir.bind(fs);
1939
+ wrapped.rmdir = (path2, options) => guard(() => options !== void 0 ? rmdir(path2, options) : rmdir(path2));
1940
+ }
1941
+ return wrapped;
1942
+ }
1943
+ function wrapTransfers(transfers, guard) {
1944
+ return {
1945
+ read: (request) => guard(() => Promise.resolve(transfers.read(request))),
1946
+ write: (request) => guard(() => Promise.resolve(transfers.write(request)))
1947
+ };
1948
+ }
1949
+
1770
1950
  // src/providers/local/LocalProvider.ts
1771
1951
  import { createReadStream } from "fs";
1772
1952
  import {
@@ -6287,7 +6467,7 @@ var SshDataWriter = class {
6287
6467
  length = 0;
6288
6468
  writeByte(value) {
6289
6469
  this.assertByte(value, "byte");
6290
- const chunk = Buffer8.allocUnsafe(1);
6470
+ const chunk = Buffer8.alloc(1);
6291
6471
  chunk.writeUInt8(value, 0);
6292
6472
  return this.push(chunk);
6293
6473
  }
@@ -6305,7 +6485,7 @@ var SshDataWriter = class {
6305
6485
  retryable: false
6306
6486
  });
6307
6487
  }
6308
- const chunk = Buffer8.allocUnsafe(4);
6488
+ const chunk = Buffer8.alloc(4);
6309
6489
  chunk.writeUInt32BE(value, 0);
6310
6490
  return this.push(chunk);
6311
6491
  }
@@ -6317,7 +6497,7 @@ var SshDataWriter = class {
6317
6497
  retryable: false
6318
6498
  });
6319
6499
  }
6320
- const chunk = Buffer8.allocUnsafe(8);
6500
+ const chunk = Buffer8.alloc(8);
6321
6501
  chunk.writeBigUInt64BE(value, 0);
6322
6502
  return this.push(chunk);
6323
6503
  }
@@ -7873,7 +8053,7 @@ function encodeSshTransportPacket(payload, options = {}) {
7873
8053
  }
7874
8054
  const padding = options.randomPadding === false ? Buffer15.alloc(paddingLength) : randomBytes2(paddingLength);
7875
8055
  const packetLength = 1 + body.length + paddingLength;
7876
- const frame = Buffer15.allocUnsafe(4 + packetLength);
8056
+ const frame = Buffer15.alloc(4 + packetLength);
7877
8057
  frame.writeUInt32BE(packetLength, 0);
7878
8058
  frame.writeUInt8(paddingLength, 4);
7879
8059
  body.copy(frame, 5);
@@ -8754,7 +8934,7 @@ function computeMac(macAlgorithm, macKey, sequence, packet, macLength) {
8754
8934
  return Buffer18.alloc(0);
8755
8935
  }
8756
8936
  const hashName = macAlgorithm === "hmac-sha2-512" ? "sha512" : "sha256";
8757
- const sequenceBuffer = Buffer18.allocUnsafe(4);
8937
+ const sequenceBuffer = Buffer18.alloc(4);
8758
8938
  sequenceBuffer.writeUInt32BE(sequence >>> 0, 0);
8759
8939
  return createHmac2(hashName, macKey).update(sequenceBuffer).update(packet).digest().subarray(0, macLength);
8760
8940
  }
@@ -9706,7 +9886,7 @@ var SftpSession = class {
9706
9886
  * serializes concurrent calls so byte ordering is preserved.
9707
9887
  */
9708
9888
  sendRaw(encodedMessage, requestId) {
9709
- const frame = Buffer21.allocUnsafe(4 + encodedMessage.length);
9889
+ const frame = Buffer21.alloc(4 + encodedMessage.length);
9710
9890
  frame.writeUInt32BE(encodedMessage.length, 0);
9711
9891
  encodedMessage.copy(frame, 4);
9712
9892
  this.channel.sendData(frame).catch((err) => {
@@ -9824,44 +10004,54 @@ var NATIVE_SFTP_ALGORITHM_PREFERENCES = {
9824
10004
  "rsa-sha2-256"
9825
10005
  ]
9826
10006
  };
9827
- var NATIVE_SFTP_PROVIDER_CAPABILITIES = {
9828
- provider: NATIVE_SFTP_PROVIDER_ID,
9829
- authentication: ["password", "keyboard-interactive", "publickey"],
9830
- list: true,
9831
- stat: true,
9832
- readStream: true,
9833
- writeStream: true,
9834
- serverSideCopy: false,
9835
- serverSideMove: false,
9836
- resumeDownload: true,
9837
- resumeUpload: true,
9838
- checksum: [],
9839
- atomicRename: false,
9840
- chmod: false,
9841
- chown: false,
9842
- symlink: true,
9843
- metadata: ["accessedAt", "group", "modifiedAt", "owner", "permissions"],
9844
- maxConcurrency: 8,
9845
- notes: [
9846
- "Native SSH/SFTP provider using the project's own protocol stack (Waves 1\u20133).",
9847
- "Supports password and keyboard-interactive authentication."
9848
- ]
9849
- };
10007
+ var NATIVE_SFTP_DEFAULT_MAX_CONCURRENCY = 8;
10008
+ function buildNativeSftpCapabilities(maxConcurrency) {
10009
+ return {
10010
+ provider: NATIVE_SFTP_PROVIDER_ID,
10011
+ authentication: ["password", "keyboard-interactive", "publickey"],
10012
+ list: true,
10013
+ stat: true,
10014
+ readStream: true,
10015
+ writeStream: true,
10016
+ serverSideCopy: false,
10017
+ serverSideMove: false,
10018
+ resumeDownload: true,
10019
+ resumeUpload: true,
10020
+ checksum: [],
10021
+ atomicRename: false,
10022
+ chmod: false,
10023
+ chown: false,
10024
+ symlink: true,
10025
+ metadata: ["accessedAt", "group", "modifiedAt", "owner", "permissions"],
10026
+ maxConcurrency,
10027
+ notes: [
10028
+ "Native SSH/SFTP provider using the project's own protocol stack (Waves 1\u20133).",
10029
+ "Supports password, keyboard-interactive, and public-key (Ed25519/RSA) authentication."
10030
+ ]
10031
+ };
10032
+ }
10033
+ var NATIVE_SFTP_PROVIDER_CAPABILITIES = buildNativeSftpCapabilities(
10034
+ NATIVE_SFTP_DEFAULT_MAX_CONCURRENCY
10035
+ );
9850
10036
  function createNativeSftpProviderFactory(options = {}) {
9851
10037
  validateNativeSftpOptions(options);
10038
+ const capabilities = buildNativeSftpCapabilities(
10039
+ options.maxConcurrency ?? NATIVE_SFTP_DEFAULT_MAX_CONCURRENCY
10040
+ );
9852
10041
  return {
9853
- capabilities: NATIVE_SFTP_PROVIDER_CAPABILITIES,
9854
- create: () => new NativeSftpProvider(options),
10042
+ capabilities,
10043
+ create: () => new NativeSftpProvider(options, capabilities),
9855
10044
  id: NATIVE_SFTP_PROVIDER_ID
9856
10045
  };
9857
10046
  }
9858
10047
  var NativeSftpProvider = class {
9859
- constructor(options) {
10048
+ constructor(options, capabilities = NATIVE_SFTP_PROVIDER_CAPABILITIES) {
9860
10049
  this.options = options;
10050
+ this.capabilities = capabilities;
9861
10051
  }
9862
10052
  options;
9863
10053
  id = NATIVE_SFTP_PROVIDER_ID;
9864
- capabilities = NATIVE_SFTP_PROVIDER_CAPABILITIES;
10054
+ capabilities;
9865
10055
  async connect(profile) {
9866
10056
  const resolved = await resolveConnectionProfileSecrets(profile);
9867
10057
  const username = requireNativeSftpUsername(resolved);
@@ -10490,6 +10680,14 @@ function validateNativeSftpOptions(options) {
10490
10680
  retryable: false
10491
10681
  });
10492
10682
  }
10683
+ if (options.maxConcurrency !== void 0 && (!Number.isInteger(options.maxConcurrency) || options.maxConcurrency <= 0)) {
10684
+ throw new ConfigurationError({
10685
+ details: { maxConcurrency: options.maxConcurrency },
10686
+ message: "Native SFTP provider maxConcurrency must be a positive integer",
10687
+ protocol: "sftp",
10688
+ retryable: false
10689
+ });
10690
+ }
10493
10691
  }
10494
10692
  export {
10495
10693
  AbortError,
@@ -10528,6 +10726,7 @@ export {
10528
10726
  createLocalProviderFactory,
10529
10727
  createMemoryProviderFactory,
10530
10728
  createOAuthTokenSecretSource,
10729
+ createPooledTransferClient,
10531
10730
  createProgressEvent,
10532
10731
  createProviderTransferExecutor,
10533
10732
  createRemoteBrowser,