@zero-transfer/core 0.4.6 → 0.4.8

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
@@ -60,6 +60,7 @@ __export(core_exports, {
60
60
  copyBetween: () => copyBetween,
61
61
  createAtomicDeployPlan: () => createAtomicDeployPlan,
62
62
  createBandwidthThrottle: () => createBandwidthThrottle,
63
+ createDefaultRetryPolicy: () => createDefaultRetryPolicy,
63
64
  createLocalProviderFactory: () => createLocalProviderFactory,
64
65
  createMemoryProviderFactory: () => createMemoryProviderFactory,
65
66
  createOAuthTokenSecretSource: () => createOAuthTokenSecretSource,
@@ -95,8 +96,10 @@ __export(core_exports, {
95
96
  parseRemoteManifest: () => parseRemoteManifest,
96
97
  redactCommand: () => redactCommand,
97
98
  redactConnectionProfile: () => redactConnectionProfile,
99
+ redactErrorForLogging: () => redactErrorForLogging,
98
100
  redactObject: () => redactObject,
99
101
  redactSecretSource: () => redactSecretSource,
102
+ redactUrlForLogging: () => redactUrlForLogging,
100
103
  redactValue: () => redactValue,
101
104
  resolveConnectionProfileSecrets: () => resolveConnectionProfileSecrets,
102
105
  resolveOpenSshHost: () => resolveOpenSshHost,
@@ -117,6 +120,68 @@ module.exports = __toCommonJS(core_exports);
117
120
  // src/client/ZeroTransfer.ts
118
121
  var import_node_events = require("events");
119
122
 
123
+ // src/logging/redaction.ts
124
+ var REDACTED = "[REDACTED]";
125
+ var SENSITIVE_KEY_PATTERN = /(?:password|passphrase|privatekey|token|secret|username|user)$/i;
126
+ var SECRET_COMMAND_PATTERN = /^(PASS|USER|ACCT)\s+(.+)$/i;
127
+ var URL_KEY_PATTERN = /(?:url|uri|href)$/i;
128
+ function isSensitiveKey(key) {
129
+ return SENSITIVE_KEY_PATTERN.test(key.replace(/[_-]/g, ""));
130
+ }
131
+ function redactCommand(command) {
132
+ return command.replace(SECRET_COMMAND_PATTERN, (_fullMatch, commandName) => {
133
+ return `${commandName.toUpperCase()} ${REDACTED}`;
134
+ });
135
+ }
136
+ function redactValue(value) {
137
+ if (typeof value === "string") {
138
+ return redactCommand(value);
139
+ }
140
+ if (Array.isArray(value)) {
141
+ return value.map((item) => redactValue(item));
142
+ }
143
+ if (value !== null && typeof value === "object") {
144
+ return redactObject(value);
145
+ }
146
+ return value;
147
+ }
148
+ function redactObject(input) {
149
+ return Object.fromEntries(
150
+ Object.entries(input).map(([key, value]) => {
151
+ if (isSensitiveKey(key)) {
152
+ return [key, REDACTED];
153
+ }
154
+ if (URL_KEY_PATTERN.test(key) && typeof value === "string") {
155
+ return [key, redactUrlForLogging(value)];
156
+ }
157
+ return [key, redactValue(value)];
158
+ })
159
+ );
160
+ }
161
+ function redactUrlForLogging(url) {
162
+ let parsed;
163
+ try {
164
+ parsed = typeof url === "string" ? new URL(url) : url;
165
+ } catch {
166
+ return REDACTED;
167
+ }
168
+ const origin = parsed.host.length > 0 ? `${parsed.protocol}//${parsed.host}` : parsed.protocol;
169
+ const query = parsed.search.length > 0 ? `?${REDACTED}` : "";
170
+ return `${origin}${parsed.pathname}${query}`;
171
+ }
172
+ function redactErrorForLogging(error) {
173
+ if (error !== null && typeof error === "object") {
174
+ const candidate = error;
175
+ if (typeof candidate.toJSON === "function") {
176
+ return redactObject(candidate.toJSON());
177
+ }
178
+ }
179
+ if (error instanceof Error) {
180
+ return redactObject({ message: error.message, name: error.name });
181
+ }
182
+ return { message: redactValue(typeof error === "string" ? error : String(error)) };
183
+ }
184
+
120
185
  // src/errors/ZeroTransferError.ts
121
186
  var ZeroTransferError = class extends Error {
122
187
  /** Stable machine-readable error code. */
@@ -158,6 +223,11 @@ var ZeroTransferError = class extends Error {
158
223
  /**
159
224
  * Serializes the error into a plain object suitable for logs or API responses.
160
225
  *
226
+ * `details` and `command` are passed through secret redaction so serialized
227
+ * errors never leak credentials, signed URLs, or raw protocol commands. The
228
+ * live {@link ZeroTransferError.details | details} property stays unredacted
229
+ * for programmatic consumers.
230
+ *
161
231
  * @returns A JSON-safe object containing public structured error fields.
162
232
  */
163
233
  toJSON() {
@@ -167,12 +237,12 @@ var ZeroTransferError = class extends Error {
167
237
  message: this.message,
168
238
  protocol: this.protocol,
169
239
  host: this.host,
170
- command: this.command,
240
+ command: this.command === void 0 ? void 0 : redactCommand(this.command),
171
241
  ftpCode: this.ftpCode,
172
242
  sftpCode: this.sftpCode,
173
243
  path: this.path,
174
244
  retryable: this.retryable,
175
- details: this.details
245
+ details: this.details === void 0 ? void 0 : redactObject(this.details)
176
246
  };
177
247
  }
178
248
  };
@@ -695,15 +765,20 @@ var ProviderRegistry = class {
695
765
  var TransferClient = class {
696
766
  /** Provider registry used by this client. */
697
767
  registry;
768
+ /** Execution defaults applied when call sites omit their own values. */
769
+ defaults;
698
770
  logger;
699
771
  /**
700
772
  * Creates a transfer client without opening any provider connections.
701
773
  *
702
- * @param options - Optional registry, provider factories, and logger.
774
+ * @param options - Optional registry, provider factories, logger, and execution defaults.
703
775
  */
704
776
  constructor(options = {}) {
705
777
  this.registry = options.registry ?? new ProviderRegistry();
706
778
  this.logger = options.logger ?? noopLogger;
779
+ if (options.defaults !== void 0) {
780
+ this.defaults = { ...options.defaults };
781
+ }
707
782
  for (const provider of options.providers ?? []) {
708
783
  this.registry.register(provider);
709
784
  }
@@ -1276,18 +1351,25 @@ var TransferEngine = class {
1276
1351
  for (let attemptNumber = 1; attemptNumber <= maxAttempts; attemptNumber += 1) {
1277
1352
  this.throwIfAborted(abortScope.signal, job);
1278
1353
  const attemptStartedAt = this.now();
1354
+ const attemptScope = createAttemptScope(
1355
+ abortScope.signal,
1356
+ options.timeout,
1357
+ job,
1358
+ attemptNumber
1359
+ );
1279
1360
  const context = this.createExecutionContext(
1280
1361
  job,
1281
1362
  attemptNumber,
1282
1363
  attemptStartedAt,
1283
1364
  options,
1284
- abortScope.signal,
1365
+ attemptScope.signal,
1285
1366
  (bytesTransferred) => {
1286
1367
  latestBytesTransferred = bytesTransferred;
1287
- }
1368
+ },
1369
+ attemptScope.notifyProgress
1288
1370
  );
1289
1371
  try {
1290
- const result = await runExecutor(executor, context, abortScope.signal, job);
1372
+ const result = await runExecutor(executor, context, attemptScope.signal, job);
1291
1373
  context.throwIfAborted();
1292
1374
  latestBytesTransferred = result.bytesTransferred;
1293
1375
  const completedAt = this.now();
@@ -1305,16 +1387,27 @@ var TransferEngine = class {
1305
1387
  summarizeError(error)
1306
1388
  );
1307
1389
  attempts.push(attempt);
1308
- if (error instanceof AbortError || error instanceof TimeoutError) {
1390
+ if (error instanceof AbortError || abortScope.signal?.aborted === true) {
1309
1391
  throw error;
1310
1392
  }
1311
- const retryInput = { attempt: attemptNumber, error, job };
1393
+ const retryInput = {
1394
+ attempt: attemptNumber,
1395
+ elapsedMs: Math.max(0, completedAt.getTime() - startedAt.getTime()),
1396
+ error,
1397
+ job
1398
+ };
1312
1399
  const shouldRetry = attemptNumber < maxAttempts && (options.retry?.shouldRetry?.(retryInput) ?? isRetryable(error));
1313
1400
  if (shouldRetry) {
1314
1401
  options.retry?.onRetry?.(retryInput);
1402
+ const delayMs = normalizeDelayMs(options.retry?.getDelayMs?.(retryInput));
1403
+ if (delayMs > 0) {
1404
+ await sleepWithAbort(delayMs, abortScope.signal, job);
1405
+ }
1315
1406
  continue;
1316
1407
  }
1317
1408
  throw createTransferFailure(job, error, attempts);
1409
+ } finally {
1410
+ attemptScope.dispose();
1318
1411
  }
1319
1412
  }
1320
1413
  throw createTransferFailure(job, void 0, attempts);
@@ -1322,12 +1415,13 @@ var TransferEngine = class {
1322
1415
  abortScope.dispose();
1323
1416
  }
1324
1417
  }
1325
- createExecutionContext(job, attempt, startedAt, options, signal, updateBytesTransferred) {
1418
+ createExecutionContext(job, attempt, startedAt, options, signal, updateBytesTransferred, notifyProgress) {
1326
1419
  const context = {
1327
1420
  attempt,
1328
1421
  job,
1329
1422
  reportProgress: (bytesTransferred, totalBytes) => {
1330
1423
  this.throwIfAborted(signal, job);
1424
+ notifyProgress();
1331
1425
  updateBytesTransferred(bytesTransferred);
1332
1426
  const progressInput = {
1333
1427
  bytesTransferred,
@@ -1396,6 +1490,96 @@ function createAbortScope(parentSignal, timeout, job) {
1396
1490
  signal: controller.signal
1397
1491
  };
1398
1492
  }
1493
+ function createAttemptScope(parentSignal, timeout, job, attempt) {
1494
+ const attemptTimeoutMs = normalizeTimeoutMs(timeout?.attemptTimeoutMs);
1495
+ const stallTimeoutMs = normalizeTimeoutMs(timeout?.stallTimeoutMs);
1496
+ if (attemptTimeoutMs === void 0 && stallTimeoutMs === void 0) {
1497
+ const scope = {
1498
+ dispose: () => void 0,
1499
+ notifyProgress: () => void 0
1500
+ };
1501
+ if (parentSignal !== void 0) scope.signal = parentSignal;
1502
+ return scope;
1503
+ }
1504
+ const controller = new AbortController();
1505
+ const retryable = timeout?.retryable ?? true;
1506
+ const abortFromParent = () => controller.abort(parentSignal?.reason);
1507
+ if (parentSignal?.aborted === true) {
1508
+ abortFromParent();
1509
+ } else {
1510
+ parentSignal?.addEventListener("abort", abortFromParent, { once: true });
1511
+ }
1512
+ const attemptTimer = attemptTimeoutMs === void 0 ? void 0 : setTimeout(() => {
1513
+ controller.abort(
1514
+ new TimeoutError({
1515
+ details: { attempt, attemptTimeoutMs, jobId: job.id, operation: job.operation },
1516
+ message: `Transfer attempt ${String(attempt)} timed out after ${String(attemptTimeoutMs)}ms: ${job.id}`,
1517
+ retryable
1518
+ })
1519
+ );
1520
+ }, attemptTimeoutMs);
1521
+ let stallTimer;
1522
+ const armStallWatchdog = () => {
1523
+ if (stallTimeoutMs === void 0 || controller.signal.aborted) return;
1524
+ if (stallTimer !== void 0) clearTimeout(stallTimer);
1525
+ stallTimer = setTimeout(() => {
1526
+ controller.abort(
1527
+ new TimeoutError({
1528
+ details: { attempt, jobId: job.id, operation: job.operation, stallTimeoutMs },
1529
+ message: `Transfer attempt ${String(attempt)} stalled (no progress for ${String(stallTimeoutMs)}ms): ${job.id}`,
1530
+ retryable
1531
+ })
1532
+ );
1533
+ }, stallTimeoutMs);
1534
+ };
1535
+ armStallWatchdog();
1536
+ return {
1537
+ dispose: () => {
1538
+ if (attemptTimer !== void 0) clearTimeout(attemptTimer);
1539
+ if (stallTimer !== void 0) clearTimeout(stallTimer);
1540
+ parentSignal?.removeEventListener("abort", abortFromParent);
1541
+ },
1542
+ notifyProgress: armStallWatchdog,
1543
+ signal: controller.signal
1544
+ };
1545
+ }
1546
+ function sleepWithAbort(delayMs, signal, job) {
1547
+ return new Promise((resolve, reject) => {
1548
+ if (signal === void 0) {
1549
+ setTimeout(resolve, delayMs);
1550
+ return;
1551
+ }
1552
+ if (signal.aborted) {
1553
+ reject(toAbortFailure(signal, job));
1554
+ return;
1555
+ }
1556
+ const rejectAbort = () => {
1557
+ clearTimeout(timer);
1558
+ reject(toAbortFailure(signal, job));
1559
+ };
1560
+ const timer = setTimeout(() => {
1561
+ signal.removeEventListener("abort", rejectAbort);
1562
+ resolve();
1563
+ }, delayMs);
1564
+ signal.addEventListener("abort", rejectAbort, { once: true });
1565
+ });
1566
+ }
1567
+ function toAbortFailure(signal, job) {
1568
+ if (signal.reason instanceof ZeroTransferError) {
1569
+ return signal.reason;
1570
+ }
1571
+ return new AbortError({
1572
+ details: { jobId: job.id, operation: job.operation },
1573
+ message: `Transfer job aborted: ${job.id}`,
1574
+ retryable: false
1575
+ });
1576
+ }
1577
+ function normalizeDelayMs(value) {
1578
+ if (value === void 0 || !Number.isFinite(value) || value <= 0) {
1579
+ return 0;
1580
+ }
1581
+ return Math.floor(value);
1582
+ }
1399
1583
  function normalizeTimeoutMs(value) {
1400
1584
  if (value === void 0 || !Number.isFinite(value) || value <= 0) {
1401
1585
  return void 0;
@@ -1564,7 +1748,7 @@ async function runRoute(options) {
1564
1748
  const executor = createProviderTransferExecutor({
1565
1749
  resolveSession: ({ role }) => sessions.get(role)
1566
1750
  });
1567
- return await engine.execute(job, executor, buildExecuteOptions(options));
1751
+ return await engine.execute(job, executor, buildExecuteOptions(options, client));
1568
1752
  } finally {
1569
1753
  if (destinationSession !== void 0) {
1570
1754
  await destinationSession.disconnect();
@@ -1601,12 +1785,14 @@ function defaultJobId(route, now) {
1601
1785
  const timestamp = (now?.() ?? /* @__PURE__ */ new Date()).getTime();
1602
1786
  return `route:${route.id}:${timestamp.toString(36)}`;
1603
1787
  }
1604
- function buildExecuteOptions(options) {
1788
+ function buildExecuteOptions(options, client) {
1605
1789
  const execute = {};
1790
+ const retry = options.retry ?? client.defaults?.retry;
1791
+ const timeout = options.timeout ?? client.defaults?.timeout;
1606
1792
  if (options.signal !== void 0) execute.signal = options.signal;
1607
- if (options.retry !== void 0) execute.retry = options.retry;
1793
+ if (retry !== void 0) execute.retry = retry;
1608
1794
  if (options.onProgress !== void 0) execute.onProgress = options.onProgress;
1609
- if (options.timeout !== void 0) execute.timeout = options.timeout;
1795
+ if (timeout !== void 0) execute.timeout = timeout;
1610
1796
  if (options.bandwidthLimit !== void 0) execute.bandwidthLimit = options.bandwidthLimit;
1611
1797
  return execute;
1612
1798
  }
@@ -1663,41 +1849,6 @@ function defaultRouteSuffix(source, destination) {
1663
1849
  return `${source}->${destination}`;
1664
1850
  }
1665
1851
 
1666
- // src/logging/redaction.ts
1667
- var REDACTED = "[REDACTED]";
1668
- var SENSITIVE_KEY_PATTERN = /(?:password|passphrase|privatekey|token|secret|username|user)$/i;
1669
- var SECRET_COMMAND_PATTERN = /^(PASS|USER|ACCT)\s+(.+)$/i;
1670
- function isSensitiveKey(key) {
1671
- return SENSITIVE_KEY_PATTERN.test(key.replace(/[_-]/g, ""));
1672
- }
1673
- function redactCommand(command) {
1674
- return command.replace(SECRET_COMMAND_PATTERN, (_fullMatch, commandName) => {
1675
- return `${commandName.toUpperCase()} ${REDACTED}`;
1676
- });
1677
- }
1678
- function redactValue(value) {
1679
- if (typeof value === "string") {
1680
- return redactCommand(value);
1681
- }
1682
- if (Array.isArray(value)) {
1683
- return value.map((item) => redactValue(item));
1684
- }
1685
- if (value !== null && typeof value === "object") {
1686
- return redactObject(value);
1687
- }
1688
- return value;
1689
- }
1690
- function redactObject(input) {
1691
- return Object.fromEntries(
1692
- Object.entries(input).map(([key, value]) => {
1693
- if (isSensitiveKey(key)) {
1694
- return [key, REDACTED];
1695
- }
1696
- return [key, redactValue(value)];
1697
- })
1698
- );
1699
- }
1700
-
1701
1852
  // src/profiles/SecretSource.ts
1702
1853
  var import_node_buffer2 = require("buffer");
1703
1854
  var import_promises = require("fs/promises");
@@ -2069,11 +2220,11 @@ var import_promises2 = require("fs/promises");
2069
2220
  var import_node_path2 = __toESM(require("path"));
2070
2221
 
2071
2222
  // src/utils/path.ts
2072
- var UNSAFE_FTP_ARGUMENT_PATTERN = /[\r\n]/;
2223
+ var UNSAFE_FTP_ARGUMENT_PATTERN = /[\r\n\0]/;
2073
2224
  function assertSafeFtpArgument(value, label = "path") {
2074
2225
  if (UNSAFE_FTP_ARGUMENT_PATTERN.test(value)) {
2075
2226
  throw new ConfigurationError({
2076
- message: `Unsafe FTP ${label}: CR and LF characters are not allowed`,
2227
+ message: `Unsafe FTP ${label}: CR, LF, and NUL characters are not allowed`,
2077
2228
  retryable: false,
2078
2229
  details: {
2079
2230
  label
@@ -3375,7 +3526,6 @@ function expandAlgorithms(values) {
3375
3526
  }
3376
3527
 
3377
3528
  // src/profiles/importers/FileZillaImporter.ts
3378
- var import_node_buffer5 = require("buffer");
3379
3529
  function importFileZillaSites(xml) {
3380
3530
  const events = tokenizeXml(xml);
3381
3531
  if (events.length === 0) {
@@ -3391,7 +3541,6 @@ function importFileZillaSites(xml) {
3391
3541
  const folderNamePending = [];
3392
3542
  let inServer = false;
3393
3543
  let serverFields = {};
3394
- let serverPasswordEncoding;
3395
3544
  let activeTag;
3396
3545
  let captureFolderName = false;
3397
3546
  for (const event of events) {
@@ -3404,13 +3553,9 @@ function importFileZillaSites(xml) {
3404
3553
  if (event.name === "Server") {
3405
3554
  inServer = true;
3406
3555
  serverFields = {};
3407
- serverPasswordEncoding = void 0;
3408
3556
  continue;
3409
3557
  }
3410
3558
  activeTag = event.name;
3411
- if (event.name === "Pass" && inServer) {
3412
- serverPasswordEncoding = event.attributes["encoding"];
3413
- }
3414
3559
  if (event.name === "Name" && !inServer && folderNamePending.length > 0) {
3415
3560
  captureFolderName = true;
3416
3561
  }
@@ -3436,7 +3581,7 @@ function importFileZillaSites(xml) {
3436
3581
  }
3437
3582
  if (event.name === "Server") {
3438
3583
  const folder = folderStack.filter((segment) => segment !== "");
3439
- const result = buildSiteFromFields(serverFields, serverPasswordEncoding);
3584
+ const result = buildSiteFromFields(serverFields);
3440
3585
  if (result.kind === "site") {
3441
3586
  sites.push({ ...result.site, folder });
3442
3587
  } else {
@@ -3448,7 +3593,6 @@ function importFileZillaSites(xml) {
3448
3593
  }
3449
3594
  inServer = false;
3450
3595
  serverFields = {};
3451
- serverPasswordEncoding = void 0;
3452
3596
  activeTag = void 0;
3453
3597
  continue;
3454
3598
  }
@@ -3457,7 +3601,7 @@ function importFileZillaSites(xml) {
3457
3601
  }
3458
3602
  return { sites, skipped };
3459
3603
  }
3460
- function buildSiteFromFields(fields, passwordEncoding) {
3604
+ function buildSiteFromFields(fields) {
3461
3605
  const name = (fields["Name"] ?? fields["Host"] ?? "Untitled").trim();
3462
3606
  const host = (fields["Host"] ?? "").trim();
3463
3607
  if (host === "") return { kind: "skipped", name };
@@ -3476,18 +3620,9 @@ function buildSiteFromFields(fields, passwordEncoding) {
3476
3620
  }
3477
3621
  const user = fields["User"]?.trim();
3478
3622
  if (user !== void 0 && user !== "") profile.username = { value: user };
3479
- let password;
3480
3623
  const rawPass = fields["Pass"];
3481
- if (rawPass !== void 0 && rawPass !== "") {
3482
- if (passwordEncoding === "base64") {
3483
- password = import_node_buffer5.Buffer.from(rawPass, "base64").toString("utf8");
3484
- } else {
3485
- password = rawPass;
3486
- }
3487
- if (password !== void 0 && password !== "") profile.password = { value: password };
3488
- }
3489
- const site = { name, profile };
3490
- if (password !== void 0) site.password = password;
3624
+ const hasStoredPassword = rawPass !== void 0 && rawPass !== "";
3625
+ const site = { hasStoredPassword, name, profile };
3491
3626
  const logonText = fields["Logontype"];
3492
3627
  if (logonText !== void 0) {
3493
3628
  const logonType = Number.parseInt(logonText.trim(), 10);
@@ -3730,6 +3865,62 @@ function mapFtp550(details) {
3730
3865
  return new PermissionDeniedError(details);
3731
3866
  }
3732
3867
 
3868
+ // src/transfers/createDefaultRetryPolicy.ts
3869
+ var DEFAULT_MAX_ATTEMPTS = 4;
3870
+ var DEFAULT_BASE_DELAY_MS = 250;
3871
+ var DEFAULT_MAX_DELAY_MS = 3e4;
3872
+ var DEFAULT_MAX_ELAPSED_MS = 3e5;
3873
+ function createDefaultRetryPolicy(options = {}) {
3874
+ const maxAttempts = normalizePositiveInteger(options.maxAttempts, DEFAULT_MAX_ATTEMPTS);
3875
+ const baseDelayMs = normalizeNonNegative(options.baseDelayMs, DEFAULT_BASE_DELAY_MS);
3876
+ const maxDelayMs = normalizeNonNegative(options.maxDelayMs, DEFAULT_MAX_DELAY_MS);
3877
+ const maxElapsedMs = normalizeNonNegative(options.maxElapsedMs, DEFAULT_MAX_ELAPSED_MS);
3878
+ const random = options.random ?? Math.random;
3879
+ return {
3880
+ getDelayMs(input) {
3881
+ const retryAfterMs = readRetryAfterMs(input.error);
3882
+ if (retryAfterMs !== void 0) {
3883
+ return retryAfterMs;
3884
+ }
3885
+ const exponentialMs = baseDelayMs * 2 ** (input.attempt - 1);
3886
+ const cappedMs = Math.min(maxDelayMs, exponentialMs);
3887
+ return Math.floor(random() * cappedMs);
3888
+ },
3889
+ maxAttempts,
3890
+ shouldRetry(input) {
3891
+ if (!(input.error instanceof ZeroTransferError) || !input.error.retryable) {
3892
+ return false;
3893
+ }
3894
+ if (input.elapsedMs >= maxElapsedMs) {
3895
+ return false;
3896
+ }
3897
+ const retryAfterMs = readRetryAfterMs(input.error);
3898
+ if (retryAfterMs !== void 0 && input.elapsedMs + retryAfterMs > maxElapsedMs) {
3899
+ return false;
3900
+ }
3901
+ return true;
3902
+ }
3903
+ };
3904
+ }
3905
+ function readRetryAfterMs(error) {
3906
+ if (!(error instanceof ZeroTransferError)) return void 0;
3907
+ const value = error.details?.["retryAfterMs"];
3908
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return void 0;
3909
+ return Math.floor(value);
3910
+ }
3911
+ function normalizePositiveInteger(value, fallback) {
3912
+ if (value === void 0 || !Number.isFinite(value) || value < 1) {
3913
+ return fallback;
3914
+ }
3915
+ return Math.floor(value);
3916
+ }
3917
+ function normalizeNonNegative(value, fallback) {
3918
+ if (value === void 0 || !Number.isFinite(value) || value < 0) {
3919
+ return fallback;
3920
+ }
3921
+ return Math.floor(value);
3922
+ }
3923
+
3733
3924
  // src/transfers/TransferPlan.ts
3734
3925
  function createTransferPlan(input) {
3735
3926
  const plan = {
@@ -3827,8 +4018,8 @@ var TransferQueue = class {
3827
4018
  this.concurrency = normalizeConcurrency(options.concurrency);
3828
4019
  this.defaultExecutor = options.executor;
3829
4020
  this.resolveExecutor = options.resolveExecutor;
3830
- this.retry = options.retry;
3831
- this.timeout = options.timeout;
4021
+ this.retry = options.retry ?? options.client?.defaults?.retry;
4022
+ this.timeout = options.timeout ?? options.client?.defaults?.timeout;
3832
4023
  this.bandwidthLimit = options.bandwidthLimit;
3833
4024
  this.onProgress = options.onProgress;
3834
4025
  this.onReceipt = options.onReceipt;
@@ -4935,6 +5126,7 @@ function isMainModule(importMetaUrl) {
4935
5126
  copyBetween,
4936
5127
  createAtomicDeployPlan,
4937
5128
  createBandwidthThrottle,
5129
+ createDefaultRetryPolicy,
4938
5130
  createLocalProviderFactory,
4939
5131
  createMemoryProviderFactory,
4940
5132
  createOAuthTokenSecretSource,
@@ -4970,8 +5162,10 @@ function isMainModule(importMetaUrl) {
4970
5162
  parseRemoteManifest,
4971
5163
  redactCommand,
4972
5164
  redactConnectionProfile,
5165
+ redactErrorForLogging,
4973
5166
  redactObject,
4974
5167
  redactSecretSource,
5168
+ redactUrlForLogging,
4975
5169
  redactValue,
4976
5170
  resolveConnectionProfileSecrets,
4977
5171
  resolveOpenSshHost,