@zero-transfer/sdk 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/classic/ftp/FtpProvider.ts
1771
1951
  import { Buffer as Buffer4 } from "buffer";
1772
1952
  import { createConnection, isIP } from "net";
@@ -3653,7 +3833,7 @@ var SshDataWriter = class {
3653
3833
  length = 0;
3654
3834
  writeByte(value) {
3655
3835
  this.assertByte(value, "byte");
3656
- const chunk = Buffer6.allocUnsafe(1);
3836
+ const chunk = Buffer6.alloc(1);
3657
3837
  chunk.writeUInt8(value, 0);
3658
3838
  return this.push(chunk);
3659
3839
  }
@@ -3671,7 +3851,7 @@ var SshDataWriter = class {
3671
3851
  retryable: false
3672
3852
  });
3673
3853
  }
3674
- const chunk = Buffer6.allocUnsafe(4);
3854
+ const chunk = Buffer6.alloc(4);
3675
3855
  chunk.writeUInt32BE(value, 0);
3676
3856
  return this.push(chunk);
3677
3857
  }
@@ -3683,7 +3863,7 @@ var SshDataWriter = class {
3683
3863
  retryable: false
3684
3864
  });
3685
3865
  }
3686
- const chunk = Buffer6.allocUnsafe(8);
3866
+ const chunk = Buffer6.alloc(8);
3687
3867
  chunk.writeBigUInt64BE(value, 0);
3688
3868
  return this.push(chunk);
3689
3869
  }
@@ -5239,7 +5419,7 @@ function encodeSshTransportPacket(payload, options = {}) {
5239
5419
  }
5240
5420
  const padding = options.randomPadding === false ? Buffer13.alloc(paddingLength) : randomBytes2(paddingLength);
5241
5421
  const packetLength = 1 + body.length + paddingLength;
5242
- const frame = Buffer13.allocUnsafe(4 + packetLength);
5422
+ const frame = Buffer13.alloc(4 + packetLength);
5243
5423
  frame.writeUInt32BE(packetLength, 0);
5244
5424
  frame.writeUInt8(paddingLength, 4);
5245
5425
  body.copy(frame, 5);
@@ -6120,7 +6300,7 @@ function computeMac(macAlgorithm, macKey, sequence, packet, macLength) {
6120
6300
  return Buffer16.alloc(0);
6121
6301
  }
6122
6302
  const hashName = macAlgorithm === "hmac-sha2-512" ? "sha512" : "sha256";
6123
- const sequenceBuffer = Buffer16.allocUnsafe(4);
6303
+ const sequenceBuffer = Buffer16.alloc(4);
6124
6304
  sequenceBuffer.writeUInt32BE(sequence >>> 0, 0);
6125
6305
  return createHmac2(hashName, macKey).update(sequenceBuffer).update(packet).digest().subarray(0, macLength);
6126
6306
  }
@@ -7072,7 +7252,7 @@ var SftpSession = class {
7072
7252
  * serializes concurrent calls so byte ordering is preserved.
7073
7253
  */
7074
7254
  sendRaw(encodedMessage, requestId) {
7075
- const frame = Buffer19.allocUnsafe(4 + encodedMessage.length);
7255
+ const frame = Buffer19.alloc(4 + encodedMessage.length);
7076
7256
  frame.writeUInt32BE(encodedMessage.length, 0);
7077
7257
  encodedMessage.copy(frame, 4);
7078
7258
  this.channel.sendData(frame).catch((err) => {
@@ -7190,44 +7370,54 @@ var NATIVE_SFTP_ALGORITHM_PREFERENCES = {
7190
7370
  "rsa-sha2-256"
7191
7371
  ]
7192
7372
  };
7193
- var NATIVE_SFTP_PROVIDER_CAPABILITIES = {
7194
- provider: NATIVE_SFTP_PROVIDER_ID,
7195
- authentication: ["password", "keyboard-interactive", "publickey"],
7196
- list: true,
7197
- stat: true,
7198
- readStream: true,
7199
- writeStream: true,
7200
- serverSideCopy: false,
7201
- serverSideMove: false,
7202
- resumeDownload: true,
7203
- resumeUpload: true,
7204
- checksum: [],
7205
- atomicRename: false,
7206
- chmod: false,
7207
- chown: false,
7208
- symlink: true,
7209
- metadata: ["accessedAt", "group", "modifiedAt", "owner", "permissions"],
7210
- maxConcurrency: 8,
7211
- notes: [
7212
- "Native SSH/SFTP provider using the project's own protocol stack (Waves 1\u20133).",
7213
- "Supports password and keyboard-interactive authentication."
7214
- ]
7215
- };
7373
+ var NATIVE_SFTP_DEFAULT_MAX_CONCURRENCY = 8;
7374
+ function buildNativeSftpCapabilities(maxConcurrency) {
7375
+ return {
7376
+ provider: NATIVE_SFTP_PROVIDER_ID,
7377
+ authentication: ["password", "keyboard-interactive", "publickey"],
7378
+ list: true,
7379
+ stat: true,
7380
+ readStream: true,
7381
+ writeStream: true,
7382
+ serverSideCopy: false,
7383
+ serverSideMove: false,
7384
+ resumeDownload: true,
7385
+ resumeUpload: true,
7386
+ checksum: [],
7387
+ atomicRename: false,
7388
+ chmod: false,
7389
+ chown: false,
7390
+ symlink: true,
7391
+ metadata: ["accessedAt", "group", "modifiedAt", "owner", "permissions"],
7392
+ maxConcurrency,
7393
+ notes: [
7394
+ "Native SSH/SFTP provider using the project's own protocol stack (Waves 1\u20133).",
7395
+ "Supports password, keyboard-interactive, and public-key (Ed25519/RSA) authentication."
7396
+ ]
7397
+ };
7398
+ }
7399
+ var NATIVE_SFTP_PROVIDER_CAPABILITIES = buildNativeSftpCapabilities(
7400
+ NATIVE_SFTP_DEFAULT_MAX_CONCURRENCY
7401
+ );
7216
7402
  function createNativeSftpProviderFactory(options = {}) {
7217
7403
  validateNativeSftpOptions(options);
7404
+ const capabilities = buildNativeSftpCapabilities(
7405
+ options.maxConcurrency ?? NATIVE_SFTP_DEFAULT_MAX_CONCURRENCY
7406
+ );
7218
7407
  return {
7219
- capabilities: NATIVE_SFTP_PROVIDER_CAPABILITIES,
7220
- create: () => new NativeSftpProvider(options),
7408
+ capabilities,
7409
+ create: () => new NativeSftpProvider(options, capabilities),
7221
7410
  id: NATIVE_SFTP_PROVIDER_ID
7222
7411
  };
7223
7412
  }
7224
7413
  var NativeSftpProvider = class {
7225
- constructor(options) {
7414
+ constructor(options, capabilities = NATIVE_SFTP_PROVIDER_CAPABILITIES) {
7226
7415
  this.options = options;
7416
+ this.capabilities = capabilities;
7227
7417
  }
7228
7418
  options;
7229
7419
  id = NATIVE_SFTP_PROVIDER_ID;
7230
- capabilities = NATIVE_SFTP_PROVIDER_CAPABILITIES;
7420
+ capabilities;
7231
7421
  async connect(profile) {
7232
7422
  const resolved = await resolveConnectionProfileSecrets(profile);
7233
7423
  const username = requireNativeSftpUsername(resolved);
@@ -7856,6 +8046,14 @@ function validateNativeSftpOptions(options) {
7856
8046
  retryable: false
7857
8047
  });
7858
8048
  }
8049
+ if (options.maxConcurrency !== void 0 && (!Number.isInteger(options.maxConcurrency) || options.maxConcurrency <= 0)) {
8050
+ throw new ConfigurationError({
8051
+ details: { maxConcurrency: options.maxConcurrency },
8052
+ message: "Native SFTP provider maxConcurrency must be a positive integer",
8053
+ protocol: "sftp",
8054
+ retryable: false
8055
+ });
8056
+ }
7859
8057
  }
7860
8058
 
7861
8059
  // src/providers/web/httpInternals.ts
@@ -8393,6 +8591,7 @@ async function collectChunks(source) {
8393
8591
 
8394
8592
  // src/providers/cloud/GoogleDriveProvider.ts
8395
8593
  import { Buffer as Buffer22 } from "buffer";
8594
+ import { randomBytes as randomBytes3 } from "crypto";
8396
8595
  var GDRIVE_API_BASE = "https://www.googleapis.com/drive/v3";
8397
8596
  var GDRIVE_UPLOAD_BASE = "https://www.googleapis.com/upload/drive/v3";
8398
8597
  var GDRIVE_FOLDER_MIME = "application/vnd.google-apps.folder";
@@ -8677,7 +8876,7 @@ var GoogleDriveTransferOperations = class {
8677
8876
  const existing = await this.findExisting(parentId, name);
8678
8877
  const metadata = { name };
8679
8878
  if (existing === void 0) metadata["parents"] = [parentId];
8680
- const boundary = `----zt-boundary-${Math.random().toString(36).slice(2)}-${Date.now().toString(36)}`;
8879
+ const boundary = `----zt-boundary-${randomBytes3(16).toString("hex")}`;
8681
8880
  const bodyParts = [];
8682
8881
  const enc = new TextEncoder();
8683
8882
  bodyParts.push(
@@ -11210,12 +11409,14 @@ function createWebDavProviderFactory(options = {}) {
11210
11409
  const secure = options.secure ?? false;
11211
11410
  const basePath = options.basePath ?? "";
11212
11411
  const fetchImpl = options.fetch ?? globalThis.fetch;
11412
+ const uploadStreaming = options.uploadStreaming ?? "when-known-size";
11213
11413
  if (typeof fetchImpl !== "function") {
11214
11414
  throw new ConfigurationError({
11215
11415
  message: "Global fetch is unavailable; supply WebDavProviderOptions.fetch explicitly",
11216
11416
  retryable: false
11217
11417
  });
11218
11418
  }
11419
+ const streamingNote = uploadStreaming === "always" ? "PUT bodies are always streamed (chunked when size is unknown)." : uploadStreaming === "never" ? "PUT bodies are buffered in memory (uploadStreaming: 'never')." : "PUT bodies stream when totalBytes is known; otherwise buffered in memory.";
11219
11420
  const capabilities = {
11220
11421
  atomicRename: false,
11221
11422
  authentication: ["anonymous", "password", "token"],
@@ -11225,7 +11426,7 @@ function createWebDavProviderFactory(options = {}) {
11225
11426
  list: true,
11226
11427
  maxConcurrency: 8,
11227
11428
  metadata: ["modifiedAt", "mimeType", "uniqueId"],
11228
- notes: ["WebDAV provider buffers PUT bodies in memory; chunked uploads are not yet supported."],
11429
+ notes: [streamingNote],
11229
11430
  provider: id,
11230
11431
  readStream: true,
11231
11432
  resumeDownload: true,
@@ -11244,7 +11445,8 @@ function createWebDavProviderFactory(options = {}) {
11244
11445
  defaultHeaders: { ...options.defaultHeaders ?? {} },
11245
11446
  fetch: fetchImpl,
11246
11447
  id,
11247
- secure
11448
+ secure,
11449
+ uploadStreaming
11248
11450
  }),
11249
11451
  id
11250
11452
  };
@@ -11278,7 +11480,8 @@ var WebDavProvider = class {
11278
11480
  capabilities: this.internals.capabilities,
11279
11481
  fetch: this.internals.fetch,
11280
11482
  headers,
11281
- id: this.internals.id
11483
+ id: this.internals.id,
11484
+ uploadStreaming: this.internals.uploadStreaming
11282
11485
  };
11283
11486
  if (profile.timeoutMs !== void 0) sessionOptions.timeoutMs = profile.timeoutMs;
11284
11487
  return new WebDavSession(sessionOptions);
@@ -11403,13 +11606,53 @@ var WebDavTransferOperations = class {
11403
11606
  }
11404
11607
  const normalized = normalizeRemotePath(request.endpoint.path);
11405
11608
  const url = resolveUrl(this.options.baseUrl, normalized);
11406
- const buffered = await collectChunks6(request.content);
11609
+ const totalBytes = request.totalBytes;
11610
+ const policy = this.options.uploadStreaming;
11611
+ const shouldStream = policy === "always" || policy === "when-known-size" && typeof totalBytes === "number" && totalBytes >= 0;
11612
+ if (!shouldStream) {
11613
+ const buffered = await collectChunks6(request.content);
11614
+ const headers2 = {
11615
+ "Content-Length": String(buffered.byteLength),
11616
+ "Content-Type": "application/octet-stream"
11617
+ };
11618
+ const init2 = {
11619
+ body: buffered,
11620
+ headers: headers2,
11621
+ method: "PUT"
11622
+ };
11623
+ if (request.signal !== void 0) init2.signal = request.signal;
11624
+ const response2 = await dispatchRequest(this.options, url, init2);
11625
+ if (!response2.ok) {
11626
+ throw mapResponseError(response2, normalized);
11627
+ }
11628
+ request.reportProgress(buffered.byteLength, buffered.byteLength);
11629
+ const result2 = {
11630
+ bytesTransferred: buffered.byteLength,
11631
+ totalBytes: buffered.byteLength
11632
+ };
11633
+ const etag2 = response2.headers.get("etag");
11634
+ if (etag2 !== null) result2.checksum = etag2;
11635
+ return result2;
11636
+ }
11637
+ let bytesTransferred = 0;
11638
+ const knownTotal = typeof totalBytes === "number" ? totalBytes : void 0;
11639
+ const stream = asyncIterableToReadableStream(request.content, (chunk) => {
11640
+ bytesTransferred += chunk.byteLength;
11641
+ if (knownTotal !== void 0) {
11642
+ request.reportProgress(bytesTransferred, knownTotal);
11643
+ } else {
11644
+ request.reportProgress(bytesTransferred);
11645
+ }
11646
+ });
11407
11647
  const headers = {
11408
- "Content-Length": String(buffered.byteLength),
11409
11648
  "Content-Type": "application/octet-stream"
11410
11649
  };
11650
+ if (knownTotal !== void 0) {
11651
+ headers["Content-Length"] = String(knownTotal);
11652
+ }
11411
11653
  const init = {
11412
- body: buffered,
11654
+ body: stream,
11655
+ duplex: "half",
11413
11656
  headers,
11414
11657
  method: "PUT"
11415
11658
  };
@@ -11418,10 +11661,9 @@ var WebDavTransferOperations = class {
11418
11661
  if (!response.ok) {
11419
11662
  throw mapResponseError(response, normalized);
11420
11663
  }
11421
- request.reportProgress(buffered.byteLength, buffered.byteLength);
11422
11664
  const result = {
11423
- bytesTransferred: buffered.byteLength,
11424
- totalBytes: buffered.byteLength
11665
+ bytesTransferred,
11666
+ ...knownTotal !== void 0 ? { totalBytes: knownTotal } : { totalBytes: bytesTransferred }
11425
11667
  };
11426
11668
  const etag = response.headers.get("etag");
11427
11669
  if (etag !== null) result.checksum = etag;
@@ -11443,6 +11685,36 @@ async function collectChunks6(source) {
11443
11685
  }
11444
11686
  return out;
11445
11687
  }
11688
+ function asyncIterableToReadableStream(source, onChunk) {
11689
+ const iterator = source[Symbol.asyncIterator]();
11690
+ return new ReadableStream({
11691
+ async pull(controller) {
11692
+ try {
11693
+ const next = await iterator.next();
11694
+ if (next.done === true) {
11695
+ controller.close();
11696
+ return;
11697
+ }
11698
+ const chunk = next.value;
11699
+ if (chunk.byteLength === 0) {
11700
+ return;
11701
+ }
11702
+ controller.enqueue(chunk);
11703
+ onChunk(chunk);
11704
+ } catch (error) {
11705
+ controller.error(error);
11706
+ }
11707
+ },
11708
+ async cancel(reason) {
11709
+ if (typeof iterator.return === "function") {
11710
+ try {
11711
+ await iterator.return(reason);
11712
+ } catch {
11713
+ }
11714
+ }
11715
+ }
11716
+ });
11717
+ }
11446
11718
  function parsePropfindResponses(xml, baseUrl) {
11447
11719
  const entries = [];
11448
11720
  const responseRegex = /<(?:[a-zA-Z0-9-]+:)?response\b[^>]*>([\s\S]*?)<\/(?:[a-zA-Z0-9-]+:)?response>/gi;
@@ -11522,6 +11794,17 @@ function parentOf(path2) {
11522
11794
  return path2.slice(0, idx);
11523
11795
  }
11524
11796
 
11797
+ // src/providers/web/S3Provider.ts
11798
+ import { createHash as createHash5 } from "crypto";
11799
+ import {
11800
+ mkdir as fsMkdir,
11801
+ readFile as fsReadFile,
11802
+ rename as fsRename,
11803
+ unlink as fsUnlink,
11804
+ writeFile as fsWriteFile
11805
+ } from "fs/promises";
11806
+ import { join as joinPath4 } from "path";
11807
+
11525
11808
  // src/providers/web/awsSigv4.ts
11526
11809
  import { createHash as createHash4, createHmac as createHmac3 } from "crypto";
11527
11810
  function signSigV4(input) {
@@ -11616,6 +11899,48 @@ function createMemoryS3MultipartResumeStore() {
11616
11899
  }
11617
11900
  };
11618
11901
  }
11902
+ function createFileSystemS3MultipartResumeStore(options) {
11903
+ const directory = options.directory;
11904
+ if (typeof directory !== "string" || directory.length === 0) {
11905
+ throw new ConfigurationError({
11906
+ message: "createFileSystemS3MultipartResumeStore requires a non-empty directory option",
11907
+ retryable: false
11908
+ });
11909
+ }
11910
+ const fileFor = (key) => {
11911
+ const hash = createHash5("sha256").update(`${key.bucket}\0${key.jobId}\0${key.path}`).digest("hex");
11912
+ return joinPath4(directory, `${hash}.json`);
11913
+ };
11914
+ return {
11915
+ async clear(key) {
11916
+ try {
11917
+ await fsUnlink(fileFor(key));
11918
+ } catch (error) {
11919
+ if (error.code !== "ENOENT") throw error;
11920
+ }
11921
+ },
11922
+ async load(key) {
11923
+ try {
11924
+ const text = await fsReadFile(fileFor(key), "utf8");
11925
+ const parsed = JSON.parse(text);
11926
+ if (typeof parsed !== "object" || parsed === null || typeof parsed.uploadId !== "string" || !Array.isArray(parsed.parts)) {
11927
+ return void 0;
11928
+ }
11929
+ return parsed;
11930
+ } catch (error) {
11931
+ if (error.code === "ENOENT") return void 0;
11932
+ throw error;
11933
+ }
11934
+ },
11935
+ async save(key, checkpoint) {
11936
+ await fsMkdir(directory, { recursive: true });
11937
+ const target = fileFor(key);
11938
+ const tmp = `${target}.${String(process.pid)}.${String(Date.now())}.tmp`;
11939
+ await fsWriteFile(tmp, JSON.stringify(checkpoint), { encoding: "utf8", mode: 384 });
11940
+ await fsRename(tmp, target);
11941
+ }
11942
+ };
11943
+ }
11619
11944
  var DEFAULT_MULTIPART_PART_SIZE = 8 * 1024 * 1024;
11620
11945
  var DEFAULT_MULTIPART_THRESHOLD = 8 * 1024 * 1024;
11621
11946
  var S3_CHECKSUM_CAPABILITIES = ["etag"];
@@ -11643,7 +11968,7 @@ function createS3ProviderFactory(options = {}) {
11643
11968
  retryable: false
11644
11969
  });
11645
11970
  }
11646
- const multipartEnabled = options.multipart?.enabled ?? false;
11971
+ const multipartEnabled = options.multipart?.enabled ?? true;
11647
11972
  const multipart = {
11648
11973
  enabled: multipartEnabled,
11649
11974
  partSizeBytes: options.multipart?.partSizeBytes ?? DEFAULT_MULTIPART_PART_SIZE,
@@ -11660,9 +11985,11 @@ function createS3ProviderFactory(options = {}) {
11660
11985
  maxConcurrency: 16,
11661
11986
  metadata: ["modifiedAt", "mimeType", "uniqueId"],
11662
11987
  notes: multipartEnabled ? [
11663
- `S3 multipart upload enabled (partSize=${String(multipart.partSizeBytes)}B, threshold=${String(multipart.thresholdBytes)}B).`
11988
+ `S3 multipart upload enabled by default (partSize=${String(multipart.partSizeBytes)}B, threshold=${String(multipart.thresholdBytes)}B).`,
11989
+ "Payloads at or below the threshold automatically fall back to single-shot PUT.",
11990
+ "Pass `multipart: { enabled: false }` to force the legacy single-shot behaviour."
11664
11991
  ] : [
11665
- "S3 provider performs single-shot PUT uploads; pass multipart.enabled to stream large objects."
11992
+ "S3 provider performs single-shot PUT uploads; entire object is buffered in memory before transmission."
11666
11993
  ],
11667
11994
  provider: id,
11668
11995
  readStream: true,
@@ -12280,15 +12607,15 @@ function getBuiltinCapabilityMatrix() {
12280
12607
  {
12281
12608
  capabilities: createS3ProviderFactory({ fetch: noopFetch }).capabilities,
12282
12609
  id: "s3",
12283
- label: "S3-compatible (single-shot uploads)"
12610
+ label: "S3-compatible (multipart uploads, default)"
12284
12611
  },
12285
12612
  {
12286
12613
  capabilities: createS3ProviderFactory({
12287
12614
  fetch: noopFetch,
12288
- multipart: { enabled: true }
12615
+ multipart: { enabled: false }
12289
12616
  }).capabilities,
12290
- id: "s3:multipart",
12291
- label: "S3-compatible (multipart uploads)"
12617
+ id: "s3:single-shot",
12618
+ label: "S3-compatible (single-shot uploads)"
12292
12619
  },
12293
12620
  {
12294
12621
  capabilities: createDropboxProviderFactory({ fetch: noopFetch }).capabilities,
@@ -14968,6 +15295,7 @@ export {
14968
15295
  ConnectionError,
14969
15296
  DEFAULT_FAILED_SUBDIR,
14970
15297
  DEFAULT_PROCESSED_SUBDIR,
15298
+ DEFAULT_SSH_ALGORITHM_PREFERENCES,
14971
15299
  FtpResponseParser,
14972
15300
  InMemoryAuditLog,
14973
15301
  MftScheduler,
@@ -14981,6 +15309,14 @@ export {
14981
15309
  REMOTE_MANIFEST_FORMAT_VERSION,
14982
15310
  RouteRegistry,
14983
15311
  ScheduleRegistry,
15312
+ SshAuthSession,
15313
+ SshConnectionManager,
15314
+ SshDataReader,
15315
+ SshDataWriter,
15316
+ SshDisconnectReason,
15317
+ SshSessionChannel,
15318
+ SshTransportConnection,
15319
+ SshTransportHandshake,
14984
15320
  TimeoutError,
14985
15321
  TransferClient,
14986
15322
  TransferEngine,
@@ -14992,6 +15328,7 @@ export {
14992
15328
  ZeroTransferError,
14993
15329
  assertSafeFtpArgument,
14994
15330
  basenameRemotePath,
15331
+ buildPublickeyCredential,
14995
15332
  buildRemoteBreadcrumbs,
14996
15333
  compareRemoteManifests,
14997
15334
  composeAuditLogs,
@@ -15001,6 +15338,7 @@ export {
15001
15338
  createAzureBlobProviderFactory,
15002
15339
  createBandwidthThrottle,
15003
15340
  createDropboxProviderFactory,
15341
+ createFileSystemS3MultipartResumeStore,
15004
15342
  createFtpProviderFactory,
15005
15343
  createFtpsProviderFactory,
15006
15344
  createGcsProviderFactory,
@@ -15015,6 +15353,7 @@ export {
15015
15353
  createOAuthTokenSecretSource,
15016
15354
  createOneDriveProviderFactory,
15017
15355
  createOutboxRoute,
15356
+ createPooledTransferClient,
15018
15357
  createProgressEvent,
15019
15358
  createProviderTransferExecutor,
15020
15359
  createRemoteBrowser,
@@ -15048,6 +15387,7 @@ export {
15048
15387
  joinRemotePath,
15049
15388
  matchKnownHosts,
15050
15389
  matchKnownHostsEntry,
15390
+ negotiateSshAlgorithms,
15051
15391
  nextCronFireAt,
15052
15392
  nextScheduleFireAt,
15053
15393
  noopLogger,