@zero-transfer/sdk 0.3.1 → 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/dist/index.cjs CHANGED
@@ -40,6 +40,7 @@ __export(index_exports, {
40
40
  ConnectionError: () => ConnectionError,
41
41
  DEFAULT_FAILED_SUBDIR: () => DEFAULT_FAILED_SUBDIR,
42
42
  DEFAULT_PROCESSED_SUBDIR: () => DEFAULT_PROCESSED_SUBDIR,
43
+ DEFAULT_SSH_ALGORITHM_PREFERENCES: () => DEFAULT_SSH_ALGORITHM_PREFERENCES,
43
44
  FtpResponseParser: () => FtpResponseParser,
44
45
  InMemoryAuditLog: () => InMemoryAuditLog,
45
46
  MftScheduler: () => MftScheduler,
@@ -53,6 +54,14 @@ __export(index_exports, {
53
54
  REMOTE_MANIFEST_FORMAT_VERSION: () => REMOTE_MANIFEST_FORMAT_VERSION,
54
55
  RouteRegistry: () => RouteRegistry,
55
56
  ScheduleRegistry: () => ScheduleRegistry,
57
+ SshAuthSession: () => SshAuthSession,
58
+ SshConnectionManager: () => SshConnectionManager,
59
+ SshDataReader: () => SshDataReader,
60
+ SshDataWriter: () => SshDataWriter,
61
+ SshDisconnectReason: () => SshDisconnectReason,
62
+ SshSessionChannel: () => SshSessionChannel,
63
+ SshTransportConnection: () => SshTransportConnection,
64
+ SshTransportHandshake: () => SshTransportHandshake,
56
65
  TimeoutError: () => TimeoutError,
57
66
  TransferClient: () => TransferClient,
58
67
  TransferEngine: () => TransferEngine,
@@ -64,6 +73,7 @@ __export(index_exports, {
64
73
  ZeroTransferError: () => ZeroTransferError,
65
74
  assertSafeFtpArgument: () => assertSafeFtpArgument,
66
75
  basenameRemotePath: () => basenameRemotePath,
76
+ buildPublickeyCredential: () => buildPublickeyCredential,
67
77
  buildRemoteBreadcrumbs: () => buildRemoteBreadcrumbs,
68
78
  compareRemoteManifests: () => compareRemoteManifests,
69
79
  composeAuditLogs: () => composeAuditLogs,
@@ -73,6 +83,7 @@ __export(index_exports, {
73
83
  createAzureBlobProviderFactory: () => createAzureBlobProviderFactory,
74
84
  createBandwidthThrottle: () => createBandwidthThrottle,
75
85
  createDropboxProviderFactory: () => createDropboxProviderFactory,
86
+ createFileSystemS3MultipartResumeStore: () => createFileSystemS3MultipartResumeStore,
76
87
  createFtpProviderFactory: () => createFtpProviderFactory,
77
88
  createFtpsProviderFactory: () => createFtpsProviderFactory,
78
89
  createGcsProviderFactory: () => createGcsProviderFactory,
@@ -87,6 +98,7 @@ __export(index_exports, {
87
98
  createOAuthTokenSecretSource: () => createOAuthTokenSecretSource,
88
99
  createOneDriveProviderFactory: () => createOneDriveProviderFactory,
89
100
  createOutboxRoute: () => createOutboxRoute,
101
+ createPooledTransferClient: () => createPooledTransferClient,
90
102
  createProgressEvent: () => createProgressEvent,
91
103
  createProviderTransferExecutor: () => createProviderTransferExecutor,
92
104
  createRemoteBrowser: () => createRemoteBrowser,
@@ -120,6 +132,7 @@ __export(index_exports, {
120
132
  joinRemotePath: () => joinRemotePath,
121
133
  matchKnownHosts: () => matchKnownHosts,
122
134
  matchKnownHostsEntry: () => matchKnownHostsEntry,
135
+ negotiateSshAlgorithms: () => negotiateSshAlgorithms,
123
136
  nextCronFireAt: () => nextCronFireAt,
124
137
  nextScheduleFireAt: () => nextScheduleFireAt,
125
138
  noopLogger: () => noopLogger,
@@ -1930,6 +1943,186 @@ function summarizeDiagnosticError(error) {
1930
1943
  return { message: String(error) };
1931
1944
  }
1932
1945
 
1946
+ // src/core/ConnectionPool.ts
1947
+ var DEFAULT_MAX_IDLE_PER_KEY = 4;
1948
+ var DEFAULT_IDLE_TIMEOUT_MS = 6e4;
1949
+ function createPooledTransferClient(inner, options = {}) {
1950
+ const maxIdlePerKey = Math.max(1, options.maxIdlePerKey ?? DEFAULT_MAX_IDLE_PER_KEY);
1951
+ const idleTimeoutMs = Math.max(0, options.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS);
1952
+ const keyOf = options.keyOf ?? defaultKeyOf;
1953
+ const state = {
1954
+ drained: false,
1955
+ idle: /* @__PURE__ */ new Map()
1956
+ };
1957
+ const release = (key, session, tainted) => {
1958
+ if (tainted || state.drained) {
1959
+ return safelyDisconnect(session);
1960
+ }
1961
+ let bucket = state.idle.get(key);
1962
+ if (bucket === void 0) {
1963
+ bucket = [];
1964
+ state.idle.set(key, bucket);
1965
+ }
1966
+ const entry = { session };
1967
+ if (idleTimeoutMs > 0) {
1968
+ entry.idleTimer = setTimeout(() => {
1969
+ evictEntry(state, key, entry);
1970
+ }, idleTimeoutMs);
1971
+ const timer = entry.idleTimer;
1972
+ if (timer !== void 0 && typeof timer.unref === "function") {
1973
+ timer.unref();
1974
+ }
1975
+ }
1976
+ bucket.push(entry);
1977
+ while (bucket.length > maxIdlePerKey) {
1978
+ const dropped = bucket.shift();
1979
+ if (dropped !== void 0) {
1980
+ clearEntryTimer(dropped);
1981
+ void safelyDisconnect(dropped.session);
1982
+ }
1983
+ }
1984
+ return Promise.resolve();
1985
+ };
1986
+ const acquire = async (profile) => {
1987
+ const key = keyOf(profile);
1988
+ const bucket = state.idle.get(key);
1989
+ if (bucket !== void 0 && bucket.length > 0) {
1990
+ const entry = bucket.pop();
1991
+ if (entry !== void 0) {
1992
+ clearEntryTimer(entry);
1993
+ if (bucket.length === 0) state.idle.delete(key);
1994
+ return { key, session: entry.session };
1995
+ }
1996
+ }
1997
+ const session = await inner.connect(profile);
1998
+ return { key, session };
1999
+ };
2000
+ return {
2001
+ connect: async (profile) => {
2002
+ const { key, session } = await acquire(profile);
2003
+ return wrapPooledSession(session, key, release);
2004
+ },
2005
+ drainPool: async () => {
2006
+ state.drained = true;
2007
+ const entries = [];
2008
+ for (const bucket of state.idle.values()) {
2009
+ for (const entry of bucket) {
2010
+ clearEntryTimer(entry);
2011
+ entries.push(entry);
2012
+ }
2013
+ }
2014
+ state.idle.clear();
2015
+ await Promise.all(entries.map((entry) => safelyDisconnect(entry.session)));
2016
+ },
2017
+ getCapabilities: ((providerId) => {
2018
+ if (providerId === void 0) return inner.getCapabilities();
2019
+ return inner.getCapabilities(providerId);
2020
+ }),
2021
+ hasProvider: (providerId) => inner.hasProvider(providerId),
2022
+ poolSize: () => {
2023
+ let total = 0;
2024
+ for (const bucket of state.idle.values()) total += bucket.length;
2025
+ return total;
2026
+ }
2027
+ };
2028
+ }
2029
+ function defaultKeyOf(profile) {
2030
+ const provider = profile.provider ?? profile.protocol ?? "unknown";
2031
+ const host = profile.host ?? "";
2032
+ const port = profile.port ?? "";
2033
+ const username = typeof profile.username === "string" ? profile.username : "";
2034
+ return `${provider}|${host}|${String(port)}|${username}`;
2035
+ }
2036
+ function evictEntry(state, key, entry) {
2037
+ const bucket = state.idle.get(key);
2038
+ if (bucket === void 0) return;
2039
+ const index = bucket.indexOf(entry);
2040
+ if (index < 0) return;
2041
+ bucket.splice(index, 1);
2042
+ if (bucket.length === 0) state.idle.delete(key);
2043
+ clearEntryTimer(entry);
2044
+ void safelyDisconnect(entry.session);
2045
+ }
2046
+ function clearEntryTimer(entry) {
2047
+ if (entry.idleTimer !== void 0) {
2048
+ clearTimeout(entry.idleTimer);
2049
+ delete entry.idleTimer;
2050
+ }
2051
+ }
2052
+ async function safelyDisconnect(session) {
2053
+ try {
2054
+ await session.disconnect();
2055
+ } catch {
2056
+ }
2057
+ }
2058
+ function isTaintingError(error) {
2059
+ return error instanceof ConnectionError || error instanceof TimeoutError || error instanceof ProtocolError;
2060
+ }
2061
+ function wrapPooledSession(session, key, release) {
2062
+ let tainted = false;
2063
+ let released = false;
2064
+ const guard = (fn) => {
2065
+ let promise;
2066
+ try {
2067
+ promise = fn();
2068
+ } catch (error) {
2069
+ if (isTaintingError(error)) tainted = true;
2070
+ return Promise.reject(error instanceof Error ? error : new Error(String(error)));
2071
+ }
2072
+ return promise.catch((error) => {
2073
+ if (isTaintingError(error)) tainted = true;
2074
+ throw error;
2075
+ });
2076
+ };
2077
+ const fs = wrapFs(session.fs, guard);
2078
+ const transfers = session.transfers === void 0 ? void 0 : wrapTransfers(session.transfers, guard);
2079
+ const wrapped = {
2080
+ capabilities: session.capabilities,
2081
+ disconnect: async () => {
2082
+ if (released) return;
2083
+ released = true;
2084
+ await release(key, session, tainted);
2085
+ },
2086
+ fs,
2087
+ provider: session.provider,
2088
+ ...transfers !== void 0 ? { transfers } : {}
2089
+ };
2090
+ if (typeof session.raw === "function") {
2091
+ const rawFn = session.raw.bind(session);
2092
+ wrapped.raw = () => rawFn();
2093
+ }
2094
+ return wrapped;
2095
+ }
2096
+ function wrapFs(fs, guard) {
2097
+ const wrapped = {
2098
+ list: (path2, options) => guard(() => options !== void 0 ? fs.list(path2, options) : fs.list(path2)),
2099
+ stat: (path2, options) => guard(() => options !== void 0 ? fs.stat(path2, options) : fs.stat(path2))
2100
+ };
2101
+ if (typeof fs.remove === "function") {
2102
+ const remove = fs.remove.bind(fs);
2103
+ wrapped.remove = (path2, options) => guard(() => options !== void 0 ? remove(path2, options) : remove(path2));
2104
+ }
2105
+ if (typeof fs.rename === "function") {
2106
+ const rename2 = fs.rename.bind(fs);
2107
+ wrapped.rename = (from, to, options) => guard(() => options !== void 0 ? rename2(from, to, options) : rename2(from, to));
2108
+ }
2109
+ if (typeof fs.mkdir === "function") {
2110
+ const mkdir2 = fs.mkdir.bind(fs);
2111
+ wrapped.mkdir = (path2, options) => guard(() => options !== void 0 ? mkdir2(path2, options) : mkdir2(path2));
2112
+ }
2113
+ if (typeof fs.rmdir === "function") {
2114
+ const rmdir = fs.rmdir.bind(fs);
2115
+ wrapped.rmdir = (path2, options) => guard(() => options !== void 0 ? rmdir(path2, options) : rmdir(path2));
2116
+ }
2117
+ return wrapped;
2118
+ }
2119
+ function wrapTransfers(transfers, guard) {
2120
+ return {
2121
+ read: (request) => guard(() => Promise.resolve(transfers.read(request))),
2122
+ write: (request) => guard(() => Promise.resolve(transfers.write(request)))
2123
+ };
2124
+ }
2125
+
1933
2126
  // src/providers/classic/ftp/FtpProvider.ts
1934
2127
  var import_node_buffer3 = require("buffer");
1935
2128
  var import_node_net = require("net");
@@ -3814,7 +4007,7 @@ var SshDataWriter = class {
3814
4007
  length = 0;
3815
4008
  writeByte(value) {
3816
4009
  this.assertByte(value, "byte");
3817
- const chunk = import_node_buffer5.Buffer.allocUnsafe(1);
4010
+ const chunk = import_node_buffer5.Buffer.alloc(1);
3818
4011
  chunk.writeUInt8(value, 0);
3819
4012
  return this.push(chunk);
3820
4013
  }
@@ -3832,7 +4025,7 @@ var SshDataWriter = class {
3832
4025
  retryable: false
3833
4026
  });
3834
4027
  }
3835
- const chunk = import_node_buffer5.Buffer.allocUnsafe(4);
4028
+ const chunk = import_node_buffer5.Buffer.alloc(4);
3836
4029
  chunk.writeUInt32BE(value, 0);
3837
4030
  return this.push(chunk);
3838
4031
  }
@@ -3844,7 +4037,7 @@ var SshDataWriter = class {
3844
4037
  retryable: false
3845
4038
  });
3846
4039
  }
3847
- const chunk = import_node_buffer5.Buffer.allocUnsafe(8);
4040
+ const chunk = import_node_buffer5.Buffer.alloc(8);
3848
4041
  chunk.writeBigUInt64BE(value, 0);
3849
4042
  return this.push(chunk);
3850
4043
  }
@@ -5400,7 +5593,7 @@ function encodeSshTransportPacket(payload, options = {}) {
5400
5593
  }
5401
5594
  const padding = options.randomPadding === false ? import_node_buffer12.Buffer.alloc(paddingLength) : (0, import_node_crypto6.randomBytes)(paddingLength);
5402
5595
  const packetLength = 1 + body.length + paddingLength;
5403
- const frame = import_node_buffer12.Buffer.allocUnsafe(4 + packetLength);
5596
+ const frame = import_node_buffer12.Buffer.alloc(4 + packetLength);
5404
5597
  frame.writeUInt32BE(packetLength, 0);
5405
5598
  frame.writeUInt8(paddingLength, 4);
5406
5599
  body.copy(frame, 5);
@@ -6276,7 +6469,7 @@ function computeMac(macAlgorithm, macKey, sequence, packet, macLength) {
6276
6469
  return import_node_buffer15.Buffer.alloc(0);
6277
6470
  }
6278
6471
  const hashName = macAlgorithm === "hmac-sha2-512" ? "sha512" : "sha256";
6279
- const sequenceBuffer = import_node_buffer15.Buffer.allocUnsafe(4);
6472
+ const sequenceBuffer = import_node_buffer15.Buffer.alloc(4);
6280
6473
  sequenceBuffer.writeUInt32BE(sequence >>> 0, 0);
6281
6474
  return (0, import_node_crypto8.createHmac)(hashName, macKey).update(sequenceBuffer).update(packet).digest().subarray(0, macLength);
6282
6475
  }
@@ -7228,7 +7421,7 @@ var SftpSession = class {
7228
7421
  * serializes concurrent calls so byte ordering is preserved.
7229
7422
  */
7230
7423
  sendRaw(encodedMessage, requestId) {
7231
- const frame = import_node_buffer18.Buffer.allocUnsafe(4 + encodedMessage.length);
7424
+ const frame = import_node_buffer18.Buffer.alloc(4 + encodedMessage.length);
7232
7425
  frame.writeUInt32BE(encodedMessage.length, 0);
7233
7426
  encodedMessage.copy(frame, 4);
7234
7427
  this.channel.sendData(frame).catch((err) => {
@@ -7346,44 +7539,54 @@ var NATIVE_SFTP_ALGORITHM_PREFERENCES = {
7346
7539
  "rsa-sha2-256"
7347
7540
  ]
7348
7541
  };
7349
- var NATIVE_SFTP_PROVIDER_CAPABILITIES = {
7350
- provider: NATIVE_SFTP_PROVIDER_ID,
7351
- authentication: ["password", "keyboard-interactive", "publickey"],
7352
- list: true,
7353
- stat: true,
7354
- readStream: true,
7355
- writeStream: true,
7356
- serverSideCopy: false,
7357
- serverSideMove: false,
7358
- resumeDownload: true,
7359
- resumeUpload: true,
7360
- checksum: [],
7361
- atomicRename: false,
7362
- chmod: false,
7363
- chown: false,
7364
- symlink: true,
7365
- metadata: ["accessedAt", "group", "modifiedAt", "owner", "permissions"],
7366
- maxConcurrency: 8,
7367
- notes: [
7368
- "Native SSH/SFTP provider using the project's own protocol stack (Waves 1\u20133).",
7369
- "Supports password and keyboard-interactive authentication."
7370
- ]
7371
- };
7542
+ var NATIVE_SFTP_DEFAULT_MAX_CONCURRENCY = 8;
7543
+ function buildNativeSftpCapabilities(maxConcurrency) {
7544
+ return {
7545
+ provider: NATIVE_SFTP_PROVIDER_ID,
7546
+ authentication: ["password", "keyboard-interactive", "publickey"],
7547
+ list: true,
7548
+ stat: true,
7549
+ readStream: true,
7550
+ writeStream: true,
7551
+ serverSideCopy: false,
7552
+ serverSideMove: false,
7553
+ resumeDownload: true,
7554
+ resumeUpload: true,
7555
+ checksum: [],
7556
+ atomicRename: false,
7557
+ chmod: false,
7558
+ chown: false,
7559
+ symlink: true,
7560
+ metadata: ["accessedAt", "group", "modifiedAt", "owner", "permissions"],
7561
+ maxConcurrency,
7562
+ notes: [
7563
+ "Native SSH/SFTP provider using the project's own protocol stack (Waves 1\u20133).",
7564
+ "Supports password, keyboard-interactive, and public-key (Ed25519/RSA) authentication."
7565
+ ]
7566
+ };
7567
+ }
7568
+ var NATIVE_SFTP_PROVIDER_CAPABILITIES = buildNativeSftpCapabilities(
7569
+ NATIVE_SFTP_DEFAULT_MAX_CONCURRENCY
7570
+ );
7372
7571
  function createNativeSftpProviderFactory(options = {}) {
7373
7572
  validateNativeSftpOptions(options);
7573
+ const capabilities = buildNativeSftpCapabilities(
7574
+ options.maxConcurrency ?? NATIVE_SFTP_DEFAULT_MAX_CONCURRENCY
7575
+ );
7374
7576
  return {
7375
- capabilities: NATIVE_SFTP_PROVIDER_CAPABILITIES,
7376
- create: () => new NativeSftpProvider(options),
7577
+ capabilities,
7578
+ create: () => new NativeSftpProvider(options, capabilities),
7377
7579
  id: NATIVE_SFTP_PROVIDER_ID
7378
7580
  };
7379
7581
  }
7380
7582
  var NativeSftpProvider = class {
7381
- constructor(options) {
7583
+ constructor(options, capabilities = NATIVE_SFTP_PROVIDER_CAPABILITIES) {
7382
7584
  this.options = options;
7585
+ this.capabilities = capabilities;
7383
7586
  }
7384
7587
  options;
7385
7588
  id = NATIVE_SFTP_PROVIDER_ID;
7386
- capabilities = NATIVE_SFTP_PROVIDER_CAPABILITIES;
7589
+ capabilities;
7387
7590
  async connect(profile) {
7388
7591
  const resolved = await resolveConnectionProfileSecrets(profile);
7389
7592
  const username = requireNativeSftpUsername(resolved);
@@ -8012,6 +8215,14 @@ function validateNativeSftpOptions(options) {
8012
8215
  retryable: false
8013
8216
  });
8014
8217
  }
8218
+ if (options.maxConcurrency !== void 0 && (!Number.isInteger(options.maxConcurrency) || options.maxConcurrency <= 0)) {
8219
+ throw new ConfigurationError({
8220
+ details: { maxConcurrency: options.maxConcurrency },
8221
+ message: "Native SFTP provider maxConcurrency must be a positive integer",
8222
+ protocol: "sftp",
8223
+ retryable: false
8224
+ });
8225
+ }
8015
8226
  }
8016
8227
 
8017
8228
  // src/providers/web/httpInternals.ts
@@ -8549,6 +8760,7 @@ async function collectChunks(source) {
8549
8760
 
8550
8761
  // src/providers/cloud/GoogleDriveProvider.ts
8551
8762
  var import_node_buffer21 = require("buffer");
8763
+ var import_node_crypto10 = require("crypto");
8552
8764
  var GDRIVE_API_BASE = "https://www.googleapis.com/drive/v3";
8553
8765
  var GDRIVE_UPLOAD_BASE = "https://www.googleapis.com/upload/drive/v3";
8554
8766
  var GDRIVE_FOLDER_MIME = "application/vnd.google-apps.folder";
@@ -8833,7 +9045,7 @@ var GoogleDriveTransferOperations = class {
8833
9045
  const existing = await this.findExisting(parentId, name);
8834
9046
  const metadata = { name };
8835
9047
  if (existing === void 0) metadata["parents"] = [parentId];
8836
- const boundary = `----zt-boundary-${Math.random().toString(36).slice(2)}-${Date.now().toString(36)}`;
9048
+ const boundary = `----zt-boundary-${(0, import_node_crypto10.randomBytes)(16).toString("hex")}`;
8837
9049
  const bodyParts = [];
8838
9050
  const enc = new TextEncoder();
8839
9051
  bodyParts.push(
@@ -11356,12 +11568,14 @@ function createWebDavProviderFactory(options = {}) {
11356
11568
  const secure = options.secure ?? false;
11357
11569
  const basePath = options.basePath ?? "";
11358
11570
  const fetchImpl = options.fetch ?? globalThis.fetch;
11571
+ const uploadStreaming = options.uploadStreaming ?? "when-known-size";
11359
11572
  if (typeof fetchImpl !== "function") {
11360
11573
  throw new ConfigurationError({
11361
11574
  message: "Global fetch is unavailable; supply WebDavProviderOptions.fetch explicitly",
11362
11575
  retryable: false
11363
11576
  });
11364
11577
  }
11578
+ 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.";
11365
11579
  const capabilities = {
11366
11580
  atomicRename: false,
11367
11581
  authentication: ["anonymous", "password", "token"],
@@ -11371,7 +11585,7 @@ function createWebDavProviderFactory(options = {}) {
11371
11585
  list: true,
11372
11586
  maxConcurrency: 8,
11373
11587
  metadata: ["modifiedAt", "mimeType", "uniqueId"],
11374
- notes: ["WebDAV provider buffers PUT bodies in memory; chunked uploads are not yet supported."],
11588
+ notes: [streamingNote],
11375
11589
  provider: id,
11376
11590
  readStream: true,
11377
11591
  resumeDownload: true,
@@ -11390,7 +11604,8 @@ function createWebDavProviderFactory(options = {}) {
11390
11604
  defaultHeaders: { ...options.defaultHeaders ?? {} },
11391
11605
  fetch: fetchImpl,
11392
11606
  id,
11393
- secure
11607
+ secure,
11608
+ uploadStreaming
11394
11609
  }),
11395
11610
  id
11396
11611
  };
@@ -11424,7 +11639,8 @@ var WebDavProvider = class {
11424
11639
  capabilities: this.internals.capabilities,
11425
11640
  fetch: this.internals.fetch,
11426
11641
  headers,
11427
- id: this.internals.id
11642
+ id: this.internals.id,
11643
+ uploadStreaming: this.internals.uploadStreaming
11428
11644
  };
11429
11645
  if (profile.timeoutMs !== void 0) sessionOptions.timeoutMs = profile.timeoutMs;
11430
11646
  return new WebDavSession(sessionOptions);
@@ -11549,13 +11765,53 @@ var WebDavTransferOperations = class {
11549
11765
  }
11550
11766
  const normalized = normalizeRemotePath(request.endpoint.path);
11551
11767
  const url = resolveUrl(this.options.baseUrl, normalized);
11552
- const buffered = await collectChunks6(request.content);
11768
+ const totalBytes = request.totalBytes;
11769
+ const policy = this.options.uploadStreaming;
11770
+ const shouldStream = policy === "always" || policy === "when-known-size" && typeof totalBytes === "number" && totalBytes >= 0;
11771
+ if (!shouldStream) {
11772
+ const buffered = await collectChunks6(request.content);
11773
+ const headers2 = {
11774
+ "Content-Length": String(buffered.byteLength),
11775
+ "Content-Type": "application/octet-stream"
11776
+ };
11777
+ const init2 = {
11778
+ body: buffered,
11779
+ headers: headers2,
11780
+ method: "PUT"
11781
+ };
11782
+ if (request.signal !== void 0) init2.signal = request.signal;
11783
+ const response2 = await dispatchRequest(this.options, url, init2);
11784
+ if (!response2.ok) {
11785
+ throw mapResponseError(response2, normalized);
11786
+ }
11787
+ request.reportProgress(buffered.byteLength, buffered.byteLength);
11788
+ const result2 = {
11789
+ bytesTransferred: buffered.byteLength,
11790
+ totalBytes: buffered.byteLength
11791
+ };
11792
+ const etag2 = response2.headers.get("etag");
11793
+ if (etag2 !== null) result2.checksum = etag2;
11794
+ return result2;
11795
+ }
11796
+ let bytesTransferred = 0;
11797
+ const knownTotal = typeof totalBytes === "number" ? totalBytes : void 0;
11798
+ const stream = asyncIterableToReadableStream(request.content, (chunk) => {
11799
+ bytesTransferred += chunk.byteLength;
11800
+ if (knownTotal !== void 0) {
11801
+ request.reportProgress(bytesTransferred, knownTotal);
11802
+ } else {
11803
+ request.reportProgress(bytesTransferred);
11804
+ }
11805
+ });
11553
11806
  const headers = {
11554
- "Content-Length": String(buffered.byteLength),
11555
11807
  "Content-Type": "application/octet-stream"
11556
11808
  };
11809
+ if (knownTotal !== void 0) {
11810
+ headers["Content-Length"] = String(knownTotal);
11811
+ }
11557
11812
  const init = {
11558
- body: buffered,
11813
+ body: stream,
11814
+ duplex: "half",
11559
11815
  headers,
11560
11816
  method: "PUT"
11561
11817
  };
@@ -11564,10 +11820,9 @@ var WebDavTransferOperations = class {
11564
11820
  if (!response.ok) {
11565
11821
  throw mapResponseError(response, normalized);
11566
11822
  }
11567
- request.reportProgress(buffered.byteLength, buffered.byteLength);
11568
11823
  const result = {
11569
- bytesTransferred: buffered.byteLength,
11570
- totalBytes: buffered.byteLength
11824
+ bytesTransferred,
11825
+ ...knownTotal !== void 0 ? { totalBytes: knownTotal } : { totalBytes: bytesTransferred }
11571
11826
  };
11572
11827
  const etag = response.headers.get("etag");
11573
11828
  if (etag !== null) result.checksum = etag;
@@ -11589,6 +11844,36 @@ async function collectChunks6(source) {
11589
11844
  }
11590
11845
  return out;
11591
11846
  }
11847
+ function asyncIterableToReadableStream(source, onChunk) {
11848
+ const iterator = source[Symbol.asyncIterator]();
11849
+ return new ReadableStream({
11850
+ async pull(controller) {
11851
+ try {
11852
+ const next = await iterator.next();
11853
+ if (next.done === true) {
11854
+ controller.close();
11855
+ return;
11856
+ }
11857
+ const chunk = next.value;
11858
+ if (chunk.byteLength === 0) {
11859
+ return;
11860
+ }
11861
+ controller.enqueue(chunk);
11862
+ onChunk(chunk);
11863
+ } catch (error) {
11864
+ controller.error(error);
11865
+ }
11866
+ },
11867
+ async cancel(reason) {
11868
+ if (typeof iterator.return === "function") {
11869
+ try {
11870
+ await iterator.return(reason);
11871
+ } catch {
11872
+ }
11873
+ }
11874
+ }
11875
+ });
11876
+ }
11592
11877
  function parsePropfindResponses(xml, baseUrl) {
11593
11878
  const entries = [];
11594
11879
  const responseRegex = /<(?:[a-zA-Z0-9-]+:)?response\b[^>]*>([\s\S]*?)<\/(?:[a-zA-Z0-9-]+:)?response>/gi;
@@ -11668,8 +11953,13 @@ function parentOf(path2) {
11668
11953
  return path2.slice(0, idx);
11669
11954
  }
11670
11955
 
11956
+ // src/providers/web/S3Provider.ts
11957
+ var import_node_crypto12 = require("crypto");
11958
+ var import_promises3 = require("fs/promises");
11959
+ var import_node_path3 = require("path");
11960
+
11671
11961
  // src/providers/web/awsSigv4.ts
11672
- var import_node_crypto10 = require("crypto");
11962
+ var import_node_crypto11 = require("crypto");
11673
11963
  function signSigV4(input) {
11674
11964
  const now = input.now ?? /* @__PURE__ */ new Date();
11675
11965
  const amzDate = formatAmzDate(now);
@@ -11739,13 +12029,13 @@ function encodeRfc3986(value) {
11739
12029
  );
11740
12030
  }
11741
12031
  function sha256Hex(data) {
11742
- return (0, import_node_crypto10.createHash)("sha256").update(data).digest("hex");
12032
+ return (0, import_node_crypto11.createHash)("sha256").update(data).digest("hex");
11743
12033
  }
11744
12034
  function hmac(key, data) {
11745
- return (0, import_node_crypto10.createHmac)("sha256", key).update(data, "utf8").digest();
12035
+ return (0, import_node_crypto11.createHmac)("sha256", key).update(data, "utf8").digest();
11746
12036
  }
11747
12037
  function hmacHex(key, data) {
11748
- return (0, import_node_crypto10.createHmac)("sha256", key).update(data, "utf8").digest("hex");
12038
+ return (0, import_node_crypto11.createHmac)("sha256", key).update(data, "utf8").digest("hex");
11749
12039
  }
11750
12040
 
11751
12041
  // src/providers/web/S3Provider.ts
@@ -11762,6 +12052,48 @@ function createMemoryS3MultipartResumeStore() {
11762
12052
  }
11763
12053
  };
11764
12054
  }
12055
+ function createFileSystemS3MultipartResumeStore(options) {
12056
+ const directory = options.directory;
12057
+ if (typeof directory !== "string" || directory.length === 0) {
12058
+ throw new ConfigurationError({
12059
+ message: "createFileSystemS3MultipartResumeStore requires a non-empty directory option",
12060
+ retryable: false
12061
+ });
12062
+ }
12063
+ const fileFor = (key) => {
12064
+ const hash = (0, import_node_crypto12.createHash)("sha256").update(`${key.bucket}\0${key.jobId}\0${key.path}`).digest("hex");
12065
+ return (0, import_node_path3.join)(directory, `${hash}.json`);
12066
+ };
12067
+ return {
12068
+ async clear(key) {
12069
+ try {
12070
+ await (0, import_promises3.unlink)(fileFor(key));
12071
+ } catch (error) {
12072
+ if (error.code !== "ENOENT") throw error;
12073
+ }
12074
+ },
12075
+ async load(key) {
12076
+ try {
12077
+ const text = await (0, import_promises3.readFile)(fileFor(key), "utf8");
12078
+ const parsed = JSON.parse(text);
12079
+ if (typeof parsed !== "object" || parsed === null || typeof parsed.uploadId !== "string" || !Array.isArray(parsed.parts)) {
12080
+ return void 0;
12081
+ }
12082
+ return parsed;
12083
+ } catch (error) {
12084
+ if (error.code === "ENOENT") return void 0;
12085
+ throw error;
12086
+ }
12087
+ },
12088
+ async save(key, checkpoint) {
12089
+ await (0, import_promises3.mkdir)(directory, { recursive: true });
12090
+ const target = fileFor(key);
12091
+ const tmp = `${target}.${String(process.pid)}.${String(Date.now())}.tmp`;
12092
+ await (0, import_promises3.writeFile)(tmp, JSON.stringify(checkpoint), { encoding: "utf8", mode: 384 });
12093
+ await (0, import_promises3.rename)(tmp, target);
12094
+ }
12095
+ };
12096
+ }
11765
12097
  var DEFAULT_MULTIPART_PART_SIZE = 8 * 1024 * 1024;
11766
12098
  var DEFAULT_MULTIPART_THRESHOLD = 8 * 1024 * 1024;
11767
12099
  var S3_CHECKSUM_CAPABILITIES = ["etag"];
@@ -11789,7 +12121,7 @@ function createS3ProviderFactory(options = {}) {
11789
12121
  retryable: false
11790
12122
  });
11791
12123
  }
11792
- const multipartEnabled = options.multipart?.enabled ?? false;
12124
+ const multipartEnabled = options.multipart?.enabled ?? true;
11793
12125
  const multipart = {
11794
12126
  enabled: multipartEnabled,
11795
12127
  partSizeBytes: options.multipart?.partSizeBytes ?? DEFAULT_MULTIPART_PART_SIZE,
@@ -11806,9 +12138,11 @@ function createS3ProviderFactory(options = {}) {
11806
12138
  maxConcurrency: 16,
11807
12139
  metadata: ["modifiedAt", "mimeType", "uniqueId"],
11808
12140
  notes: multipartEnabled ? [
11809
- `S3 multipart upload enabled (partSize=${String(multipart.partSizeBytes)}B, threshold=${String(multipart.thresholdBytes)}B).`
12141
+ `S3 multipart upload enabled by default (partSize=${String(multipart.partSizeBytes)}B, threshold=${String(multipart.thresholdBytes)}B).`,
12142
+ "Payloads at or below the threshold automatically fall back to single-shot PUT.",
12143
+ "Pass `multipart: { enabled: false }` to force the legacy single-shot behaviour."
11810
12144
  ] : [
11811
- "S3 provider performs single-shot PUT uploads; pass multipart.enabled to stream large objects."
12145
+ "S3 provider performs single-shot PUT uploads; entire object is buffered in memory before transmission."
11812
12146
  ],
11813
12147
  provider: id,
11814
12148
  readStream: true,
@@ -12426,15 +12760,15 @@ function getBuiltinCapabilityMatrix() {
12426
12760
  {
12427
12761
  capabilities: createS3ProviderFactory({ fetch: noopFetch }).capabilities,
12428
12762
  id: "s3",
12429
- label: "S3-compatible (single-shot uploads)"
12763
+ label: "S3-compatible (multipart uploads, default)"
12430
12764
  },
12431
12765
  {
12432
12766
  capabilities: createS3ProviderFactory({
12433
12767
  fetch: noopFetch,
12434
- multipart: { enabled: true }
12768
+ multipart: { enabled: false }
12435
12769
  }).capabilities,
12436
- id: "s3:multipart",
12437
- label: "S3-compatible (multipart uploads)"
12770
+ id: "s3:single-shot",
12771
+ label: "S3-compatible (single-shot uploads)"
12438
12772
  },
12439
12773
  {
12440
12774
  capabilities: createDropboxProviderFactory({ fetch: noopFetch }).capabilities,
@@ -14522,9 +14856,9 @@ function deepFreeze(value) {
14522
14856
  }
14523
14857
 
14524
14858
  // src/mft/webhooks.ts
14525
- var import_node_crypto11 = require("crypto");
14859
+ var import_node_crypto13 = require("crypto");
14526
14860
  function signWebhookPayload(payload, secret, timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
14527
- const mac = (0, import_node_crypto11.createHmac)("sha256", secret);
14861
+ const mac = (0, import_node_crypto13.createHmac)("sha256", secret);
14528
14862
  mac.update(`${timestamp}.${payload}`);
14529
14863
  return { digest: mac.digest("hex"), timestamp };
14530
14864
  }
@@ -15115,6 +15449,7 @@ var defaultRunner = ({ client, route, signal }) => {
15115
15449
  ConnectionError,
15116
15450
  DEFAULT_FAILED_SUBDIR,
15117
15451
  DEFAULT_PROCESSED_SUBDIR,
15452
+ DEFAULT_SSH_ALGORITHM_PREFERENCES,
15118
15453
  FtpResponseParser,
15119
15454
  InMemoryAuditLog,
15120
15455
  MftScheduler,
@@ -15128,6 +15463,14 @@ var defaultRunner = ({ client, route, signal }) => {
15128
15463
  REMOTE_MANIFEST_FORMAT_VERSION,
15129
15464
  RouteRegistry,
15130
15465
  ScheduleRegistry,
15466
+ SshAuthSession,
15467
+ SshConnectionManager,
15468
+ SshDataReader,
15469
+ SshDataWriter,
15470
+ SshDisconnectReason,
15471
+ SshSessionChannel,
15472
+ SshTransportConnection,
15473
+ SshTransportHandshake,
15131
15474
  TimeoutError,
15132
15475
  TransferClient,
15133
15476
  TransferEngine,
@@ -15139,6 +15482,7 @@ var defaultRunner = ({ client, route, signal }) => {
15139
15482
  ZeroTransferError,
15140
15483
  assertSafeFtpArgument,
15141
15484
  basenameRemotePath,
15485
+ buildPublickeyCredential,
15142
15486
  buildRemoteBreadcrumbs,
15143
15487
  compareRemoteManifests,
15144
15488
  composeAuditLogs,
@@ -15148,6 +15492,7 @@ var defaultRunner = ({ client, route, signal }) => {
15148
15492
  createAzureBlobProviderFactory,
15149
15493
  createBandwidthThrottle,
15150
15494
  createDropboxProviderFactory,
15495
+ createFileSystemS3MultipartResumeStore,
15151
15496
  createFtpProviderFactory,
15152
15497
  createFtpsProviderFactory,
15153
15498
  createGcsProviderFactory,
@@ -15162,6 +15507,7 @@ var defaultRunner = ({ client, route, signal }) => {
15162
15507
  createOAuthTokenSecretSource,
15163
15508
  createOneDriveProviderFactory,
15164
15509
  createOutboxRoute,
15510
+ createPooledTransferClient,
15165
15511
  createProgressEvent,
15166
15512
  createProviderTransferExecutor,
15167
15513
  createRemoteBrowser,
@@ -15195,6 +15541,7 @@ var defaultRunner = ({ client, route, signal }) => {
15195
15541
  joinRemotePath,
15196
15542
  matchKnownHosts,
15197
15543
  matchKnownHostsEntry,
15544
+ negotiateSshAlgorithms,
15198
15545
  nextCronFireAt,
15199
15546
  nextScheduleFireAt,
15200
15547
  noopLogger,