@c3-oss/prosa 0.8.0 → 0.8.1

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/bin/prosa.js CHANGED
@@ -28398,8 +28398,9 @@ import path2 from "path";
28398
28398
  import { request as httpRequest } from "http";
28399
28399
  import { request as httpsRequest } from "https";
28400
28400
  import { OBJECT_PACK_BINARY_CONTENT_TYPE, encodeBinaryObjectPack } from "@c3-oss/prosa-sync";
28401
- var OBJECT_UPLOAD_MAX_ATTEMPTS = 4;
28402
- var OBJECT_UPLOAD_BASE_BACKOFF_MS = 250;
28401
+ var OBJECT_UPLOAD_MAX_ATTEMPTS = 6;
28402
+ var OBJECT_UPLOAD_BASE_BACKOFF_MS = 500;
28403
+ var OBJECT_UPLOAD_MAX_BACKOFF_MS = 15e3;
28403
28404
  function trimTrailingSlash(url) {
28404
28405
  return url.endsWith("/") ? url.slice(0, -1) : url;
28405
28406
  }
@@ -28424,7 +28425,8 @@ __name(retryAfterMs, "retryAfterMs");
28424
28425
  function objectUploadBackoffMs(attempt, headers) {
28425
28426
  const retryAfter = headers ? retryAfterMs(headers) : void 0;
28426
28427
  if (retryAfter !== void 0) return retryAfter;
28427
- return OBJECT_UPLOAD_BASE_BACKOFF_MS * 2 ** attempt;
28428
+ const exponential = Math.min(OBJECT_UPLOAD_MAX_BACKOFF_MS, OBJECT_UPLOAD_BASE_BACKOFF_MS * 2 ** attempt);
28429
+ return Math.floor(exponential + Math.random() * Math.min(250, exponential));
28428
28430
  }
28429
28431
  __name(objectUploadBackoffMs, "objectUploadBackoffMs");
28430
28432
  function isRetryableObjectUploadStatus(status) {
@@ -28438,10 +28440,21 @@ function isRetryableNetworkError(err) {
28438
28440
  return code != null && [
28439
28441
  "ECONNRESET",
28440
28442
  "ETIMEDOUT",
28441
- "EPIPE"
28443
+ "EPIPE",
28444
+ "ECONNREFUSED",
28445
+ "UND_ERR_SOCKET",
28446
+ "UND_ERR_CONNECT_TIMEOUT"
28442
28447
  ].includes(code);
28443
28448
  }
28444
28449
  __name(isRetryableNetworkError, "isRetryableNetworkError");
28450
+ function networkErrorReason(err) {
28451
+ if (err instanceof Error) {
28452
+ const code = err.code ?? err.cause?.code;
28453
+ return code ? `${err.message} (${code})` : err.message;
28454
+ }
28455
+ return String(err);
28456
+ }
28457
+ __name(networkErrorReason, "networkErrorReason");
28445
28458
  function isUnsupportedBinaryObjectPackResponse(status, text) {
28446
28459
  return status === 415 || status === 400 && /Unsupported Media Type|JSON body required/i.test(text);
28447
28460
  }
@@ -28539,12 +28552,16 @@ var ProsaApiClient = class {
28539
28552
  baseUrl;
28540
28553
  fetchFn;
28541
28554
  hasInjectedFetch;
28555
+ onRetry;
28556
+ onRequestSuccess;
28542
28557
  token;
28543
28558
  tenantId;
28544
28559
  constructor(opts) {
28545
28560
  this.baseUrl = trimTrailingSlash(opts.baseUrl);
28546
28561
  this.fetchFn = opts.fetch ?? globalThis.fetch;
28547
28562
  this.hasInjectedFetch = Boolean(opts.fetch);
28563
+ this.onRetry = opts.onRetry;
28564
+ this.onRequestSuccess = opts.onRequestSuccess;
28548
28565
  this.token = opts.token;
28549
28566
  this.tenantId = opts.tenantId;
28550
28567
  }
@@ -28575,6 +28592,90 @@ var ProsaApiClient = class {
28575
28592
  });
28576
28593
  return this.parseTrpc(path14, response);
28577
28594
  }
28595
+ async trpcMutationRetriable(path14, input, opts) {
28596
+ return this.retriableFetch({
28597
+ operation: opts.operation,
28598
+ retryHttpStatusBeforeParse: false,
28599
+ retryParseErrorsOnRetryableStatus: true,
28600
+ retryStructuredErrorsOnRetryableStatus: opts.retryStructuredErrorsOnRetryableStatus,
28601
+ request: /* @__PURE__ */ __name(() => this.fetchFn(`${this.baseUrl}/trpc/${path14}`, {
28602
+ method: "POST",
28603
+ headers: this.headers({
28604
+ "content-type": "application/json",
28605
+ ...opts.headers
28606
+ }),
28607
+ body: JSON.stringify(input ?? {})
28608
+ }), "request"),
28609
+ parse: /* @__PURE__ */ __name((response) => this.parseTrpc(path14, response), "parse")
28610
+ });
28611
+ }
28612
+ async retriableFetch(opts) {
28613
+ const { operation: operation2, request: request2, parse: parse3 } = opts;
28614
+ let lastError;
28615
+ for (let attempt = 0; attempt < OBJECT_UPLOAD_MAX_ATTEMPTS; attempt += 1) {
28616
+ let response;
28617
+ try {
28618
+ response = await request2();
28619
+ } catch (err) {
28620
+ lastError = err;
28621
+ if (!isRetryableNetworkError(err) || attempt >= OBJECT_UPLOAD_MAX_ATTEMPTS - 1) {
28622
+ throw this.wrapRetriedError(operation2, attempt + 1, err);
28623
+ }
28624
+ const delayMs = objectUploadBackoffMs(attempt);
28625
+ this.onRetry?.({
28626
+ operation: operation2,
28627
+ attempt: attempt + 1,
28628
+ maxAttempts: OBJECT_UPLOAD_MAX_ATTEMPTS,
28629
+ delayMs,
28630
+ reason: networkErrorReason(err)
28631
+ });
28632
+ await sleep(delayMs);
28633
+ continue;
28634
+ }
28635
+ if (opts.retryHttpStatusBeforeParse !== false && isRetryableObjectUploadStatus(response.status) && attempt < OBJECT_UPLOAD_MAX_ATTEMPTS - 1) {
28636
+ const delayMs = objectUploadBackoffMs(attempt, response.headers);
28637
+ this.onRetry?.({
28638
+ operation: operation2,
28639
+ attempt: attempt + 1,
28640
+ maxAttempts: OBJECT_UPLOAD_MAX_ATTEMPTS,
28641
+ delayMs,
28642
+ reason: `HTTP ${response.status}`
28643
+ });
28644
+ await response.arrayBuffer().catch(() => void 0);
28645
+ await sleep(delayMs);
28646
+ continue;
28647
+ }
28648
+ try {
28649
+ const parsed = await parse3(response);
28650
+ this.onRequestSuccess?.({
28651
+ operation: operation2,
28652
+ attempts: attempt + 1
28653
+ });
28654
+ return parsed;
28655
+ } catch (err) {
28656
+ if (opts.retryParseErrorsOnRetryableStatus && (opts.retryStructuredErrorsOnRetryableStatus !== false || !(err instanceof ProsaApiError)) && isRetryableObjectUploadStatus(response.status) && attempt < OBJECT_UPLOAD_MAX_ATTEMPTS - 1) {
28657
+ const delayMs = objectUploadBackoffMs(attempt, response.headers);
28658
+ this.onRetry?.({
28659
+ operation: operation2,
28660
+ attempt: attempt + 1,
28661
+ maxAttempts: OBJECT_UPLOAD_MAX_ATTEMPTS,
28662
+ delayMs,
28663
+ reason: `HTTP ${response.status}: ${networkErrorReason(err)}`
28664
+ });
28665
+ await sleep(delayMs);
28666
+ continue;
28667
+ }
28668
+ throw this.wrapRetriedError(operation2, attempt + 1, err);
28669
+ }
28670
+ }
28671
+ throw this.wrapRetriedError(operation2, OBJECT_UPLOAD_MAX_ATTEMPTS, lastError);
28672
+ }
28673
+ wrapRetriedError(operation2, attempts, err) {
28674
+ if (err instanceof ProsaApiError) return err;
28675
+ if (attempts <= 1) return err instanceof Error ? err : new Error(String(err));
28676
+ const reason = networkErrorReason(err);
28677
+ return new CliUserError(`${operation2} failed after ${attempts} attempts: ${reason}`);
28678
+ }
28578
28679
  async parseTrpc(path14, response) {
28579
28680
  const text = await response.text();
28580
28681
  if (!text) throw new CliUserError(`${path14}: empty response (status ${response.status})`);
@@ -28684,14 +28785,18 @@ var ProsaApiClient = class {
28684
28785
  return this.trpcMutation("sync.planUpload", input);
28685
28786
  }
28686
28787
  async syncCommitUpload(input, opts = {}) {
28687
- return this.trpcMutation("sync.commitUpload", input, {
28788
+ return this.trpcMutationRetriable("sync.commitUpload", input, {
28789
+ operation: "sync.commitUpload",
28688
28790
  headers: opts.idempotencyKey ? {
28689
28791
  "idempotency-key": opts.idempotencyKey
28690
- } : void 0
28792
+ } : void 0,
28793
+ retryStructuredErrorsOnRetryableStatus: false
28691
28794
  });
28692
28795
  }
28693
28796
  async syncVerifyPromotion(input) {
28694
- return this.trpcMutation("sync.verifyPromotion", input);
28797
+ return this.trpcMutationRetriable("sync.verifyPromotion", input, {
28798
+ operation: "sync.verifyPromotion"
28799
+ });
28695
28800
  }
28696
28801
  async syncAckCleanup(input) {
28697
28802
  return this.trpcMutation("sync.ackCleanup", input);
@@ -28702,21 +28807,9 @@ var ProsaApiClient = class {
28702
28807
  } : void 0);
28703
28808
  }
28704
28809
  async uploadObjectBytes(input) {
28705
- let lastError;
28706
- for (let attempt = 0; attempt < OBJECT_UPLOAD_MAX_ATTEMPTS; attempt += 1) {
28707
- try {
28708
- return await this.uploadObjectBytesOnce(input, attempt);
28709
- } catch (err) {
28710
- lastError = err;
28711
- if (!isRetryableNetworkError(err) || attempt >= OBJECT_UPLOAD_MAX_ATTEMPTS - 1) {
28712
- throw err;
28713
- }
28714
- await sleep(objectUploadBackoffMs(attempt));
28715
- }
28716
- }
28717
- throw lastError instanceof Error ? lastError : new CliUserError("object upload failed");
28810
+ return this.uploadObjectBytesOnce(input);
28718
28811
  }
28719
- async uploadObjectBytesOnce(input, attempt) {
28812
+ async uploadObjectBytesOnce(input) {
28720
28813
  const url = new URL(`${this.baseUrl}/objects/${input.objectId}`);
28721
28814
  url.searchParams.set("batchId", input.batchId);
28722
28815
  url.searchParams.set("hash", input.hash);
@@ -28724,55 +28817,65 @@ var ProsaApiClient = class {
28724
28817
  url.searchParams.set("uncompressed", String(input.uncompressedSize));
28725
28818
  url.searchParams.set("compression", input.compression ?? "zstd");
28726
28819
  if (input.transportHash) url.searchParams.set("transportHash", input.transportHash);
28727
- const response = await this.fetchFn(url.toString(), {
28728
- method: "PUT",
28729
- headers: this.headers({
28730
- "content-type": "application/octet-stream"
28731
- }),
28732
- body: input.bytes
28820
+ return this.retriableFetch({
28821
+ operation: "object PUT upload",
28822
+ request: /* @__PURE__ */ __name(() => this.fetchFn(url.toString(), {
28823
+ method: "PUT",
28824
+ headers: this.headers({
28825
+ "content-type": "application/octet-stream"
28826
+ }),
28827
+ body: input.bytes
28828
+ }), "request"),
28829
+ parse: /* @__PURE__ */ __name(async (response) => {
28830
+ const text = await response.text();
28831
+ if (response.status >= 400) {
28832
+ throw new CliUserError(`object upload failed: ${response.status} ${text}`);
28833
+ }
28834
+ const parsed = JSON.parse(text);
28835
+ return {
28836
+ alreadyExisted: Boolean(parsed.alreadyExisted)
28837
+ };
28838
+ }, "parse")
28733
28839
  });
28734
- const text = await response.text();
28735
- if (response.status >= 400) {
28736
- if (isRetryableObjectUploadStatus(response.status) && attempt < OBJECT_UPLOAD_MAX_ATTEMPTS - 1) {
28737
- await sleep(objectUploadBackoffMs(attempt, response.headers));
28738
- return this.uploadObjectBytesOnce(input, attempt + 1);
28739
- }
28740
- throw new CliUserError(`object upload failed: ${response.status} ${text}`);
28741
- }
28742
- const parsed = JSON.parse(text);
28743
- return {
28744
- alreadyExisted: Boolean(parsed.alreadyExisted)
28745
- };
28746
28840
  }
28747
28841
  async uploadObjectPack(input) {
28748
28842
  const url = new URL(`${this.baseUrl}/object-packs`);
28749
28843
  url.searchParams.set("batchId", input.batchId);
28750
28844
  const prepared = this.prepareObjectPackUpload(input.objects);
28751
- const binaryResponse = await this.fetchFn(url.toString(), {
28752
- method: "POST",
28753
- headers: this.headers({
28754
- "content-type": OBJECT_PACK_BINARY_CONTENT_TYPE
28755
- }),
28756
- body: encodeBinaryObjectPack({
28757
- entries: prepared.entries,
28758
- payload: prepared.payload
28759
- })
28845
+ const binary = await this.retriableFetch({
28846
+ operation: "object pack binary upload",
28847
+ request: /* @__PURE__ */ __name(() => this.fetchFn(url.toString(), {
28848
+ method: "POST",
28849
+ headers: this.headers({
28850
+ "content-type": OBJECT_PACK_BINARY_CONTENT_TYPE
28851
+ }),
28852
+ body: encodeBinaryObjectPack({
28853
+ entries: prepared.entries,
28854
+ payload: prepared.payload
28855
+ })
28856
+ }), "request"),
28857
+ parse: /* @__PURE__ */ __name(async (response) => ({
28858
+ status: response.status,
28859
+ text: await response.text()
28860
+ }), "parse")
28760
28861
  });
28761
- const binaryText = await binaryResponse.text();
28762
- if (!isUnsupportedBinaryObjectPackResponse(binaryResponse.status, binaryText)) {
28763
- return this.parseObjectPackUploadResponse(binaryResponse.status, binaryText);
28764
- }
28765
- const fallbackResponse = await this.fetchFn(url.toString(), {
28766
- method: "POST",
28767
- headers: this.headers({
28768
- "content-type": "application/json"
28769
- }),
28770
- body: JSON.stringify({
28771
- bytesBase64: prepared.payload.toString("base64"),
28772
- entries: prepared.entries
28773
- })
28862
+ if (!isUnsupportedBinaryObjectPackResponse(binary.status, binary.text)) {
28863
+ return this.parseObjectPackUploadResponse(binary.status, binary.text);
28864
+ }
28865
+ return this.retriableFetch({
28866
+ operation: "object pack JSON upload",
28867
+ request: /* @__PURE__ */ __name(() => this.fetchFn(url.toString(), {
28868
+ method: "POST",
28869
+ headers: this.headers({
28870
+ "content-type": "application/json"
28871
+ }),
28872
+ body: JSON.stringify({
28873
+ bytesBase64: prepared.payload.toString("base64"),
28874
+ entries: prepared.entries
28875
+ })
28876
+ }), "request"),
28877
+ parse: /* @__PURE__ */ __name(async (response) => this.parseObjectPackUploadResponse(response.status, await response.text()), "parse")
28774
28878
  });
28775
- return this.parseObjectPackUploadResponse(fallbackResponse.status, await fallbackResponse.text());
28776
28879
  }
28777
28880
  prepareObjectPackUpload(objects) {
28778
28881
  let offset = 0;
@@ -53671,6 +53774,46 @@ __name(runReadUploadPipeline, "runReadUploadPipeline");
53671
53774
  // src/cli/sync/promotion.ts
53672
53775
  import { rm as rm4 } from "fs/promises";
53673
53776
  import path11 from "path";
53777
+ var AdaptiveUploadConcurrencyController = class {
53778
+ static {
53779
+ __name(this, "AdaptiveUploadConcurrencyController");
53780
+ }
53781
+ ceiling;
53782
+ onChange;
53783
+ value;
53784
+ successStreak = 0;
53785
+ constructor(ceiling, onChange) {
53786
+ this.ceiling = ceiling;
53787
+ this.onChange = onChange;
53788
+ this.value = Math.max(1, ceiling);
53789
+ }
53790
+ current() {
53791
+ return this.value;
53792
+ }
53793
+ recordRetry() {
53794
+ const previous = this.value;
53795
+ this.value = Math.max(1, Math.floor(this.value / 2));
53796
+ this.successStreak = 0;
53797
+ if (this.value !== previous) this.onChange?.({
53798
+ previous,
53799
+ current: this.value,
53800
+ reason: "retry"
53801
+ });
53802
+ }
53803
+ recordSuccess() {
53804
+ if (this.value >= this.ceiling) return;
53805
+ this.successStreak += 1;
53806
+ if (this.successStreak < 10) return;
53807
+ this.successStreak = 0;
53808
+ const previous = this.value;
53809
+ this.value = Math.min(this.ceiling, this.value + 1);
53810
+ if (this.value !== previous) this.onChange?.({
53811
+ previous,
53812
+ current: this.value,
53813
+ reason: "success"
53814
+ });
53815
+ }
53816
+ };
53674
53817
  var BLAKE3_HEX_RE = /^[0-9a-f]{64}$/i;
53675
53818
  var OBJECT_PACK_ENTRY_LIMIT = 1024;
53676
53819
  var DEFAULT_OBJECT_PACK_MAX_BYTES = 8 * 1024 * 1024;
@@ -53733,9 +53876,10 @@ async function uploadObjectPut(client, batchId, object) {
53733
53876
  });
53734
53877
  }
53735
53878
  __name(uploadObjectPut, "uploadObjectPut");
53736
- async function uploadMissingCasObjects({ client, batchId, missingObjects, objectConcurrency, maxObjectPackBytes = DEFAULT_OBJECT_PACK_MAX_BYTES }) {
53879
+ async function uploadMissingCasObjects({ client, batchId, missingObjects, objectConcurrency, uploadConcurrency, maxObjectPackBytes = DEFAULT_OBJECT_PACK_MAX_BYTES }) {
53737
53880
  const { packs, putObjects } = splitMissingObjectUploads(missingObjects, maxObjectPackBytes);
53738
- await mapConcurrent(packs, objectConcurrency, async (pack) => {
53881
+ const concurrency = /* @__PURE__ */ __name(() => uploadConcurrency?.current() ?? objectConcurrency, "concurrency");
53882
+ await mapConcurrent(packs, concurrency(), async (pack) => {
53739
53883
  await client.uploadObjectPack({
53740
53884
  batchId,
53741
53885
  objects: pack.map(({ entry, bytes }) => ({
@@ -53744,7 +53888,7 @@ async function uploadMissingCasObjects({ client, batchId, missingObjects, object
53744
53888
  }))
53745
53889
  });
53746
53890
  });
53747
- await mapConcurrent(putObjects, objectConcurrency, async (object) => {
53891
+ await mapConcurrent(putObjects, concurrency(), async (object) => {
53748
53892
  await uploadObjectPut(client, batchId, object);
53749
53893
  });
53750
53894
  return {
@@ -54016,6 +54160,7 @@ async function uploadMissingCasObjectsWithReadPipeline(opts) {
54016
54160
  batchId: opts.batchId,
54017
54161
  missingObjects: batch,
54018
54162
  objectConcurrency: opts.objectConcurrency,
54163
+ uploadConcurrency: opts.uploadConcurrency,
54019
54164
  ...opts.maxObjectPackBytes ? {
54020
54165
  maxObjectPackBytes: opts.maxObjectPackBytes
54021
54166
  } : {}
@@ -54725,7 +54870,7 @@ function readArtifactChunk(bundle, afterId, limit) {
54725
54870
  };
54726
54871
  }
54727
54872
  __name(readArtifactChunk, "readArtifactChunk");
54728
- async function promoteChunk({ client, deviceId, storePath, casObjects, projection, label, metrics, objectConcurrency, maxObjectPackBytes, verbose }) {
54873
+ async function promoteChunk({ client, deviceId, storePath, casObjects, projection, label, metrics, objectConcurrency, uploadConcurrency, maxObjectPackBytes, verbose }) {
54729
54874
  const objectEntries = casObjects.map((c6) => c6.entry);
54730
54875
  const totalStart = Date.now();
54731
54876
  const planStart = Date.now();
@@ -54751,6 +54896,7 @@ async function promoteChunk({ client, deviceId, storePath, casObjects, projectio
54751
54896
  storePath,
54752
54897
  missingObjects,
54753
54898
  objectConcurrency,
54899
+ uploadConcurrency,
54754
54900
  ...maxObjectPackBytes ? {
54755
54901
  maxObjectPackBytes
54756
54902
  } : {},
@@ -54862,7 +55008,7 @@ async function promotePhase(tasks, concurrency, worker) {
54862
55008
  return results;
54863
55009
  }
54864
55010
  __name(promotePhase, "promotePhase");
54865
- async function promoteChunkedUpload({ client, deviceId, storePath, bundle, maxObjectsPerPlan, maxRowsPerCommit, maxObjectPackBytes, objectConcurrency, batchConcurrency, verbose, progress, totalBatches, checkpoint }) {
55011
+ async function promoteChunkedUpload({ client, deviceId, storePath, bundle, maxObjectsPerPlan, maxRowsPerCommit, maxObjectPackBytes, objectConcurrency, uploadConcurrency, batchConcurrency, verbose, progress, totalBatches, checkpoint }) {
54866
55012
  let batchCount = 0;
54867
55013
  let lastReceipt = null;
54868
55014
  let metrics = emptySyncMetrics(objectConcurrency);
@@ -54887,6 +55033,7 @@ async function promoteChunkedUpload({ client, deviceId, storePath, bundle, maxOb
54887
55033
  projection: toProjection(chunk.rows),
54888
55034
  label: `${label} batch ${phaseStart + cursor.sequence}`,
54889
55035
  objectConcurrency,
55036
+ uploadConcurrency,
54890
55037
  ...maxObjectPackBytes ? {
54891
55038
  maxObjectPackBytes
54892
55039
  } : {},
@@ -54978,6 +55125,7 @@ async function promoteChunkedUpload({ client, deviceId, storePath, bundle, maxOb
54978
55125
  label: `chunk ${batchCount}`,
54979
55126
  metrics,
54980
55127
  objectConcurrency,
55128
+ uploadConcurrency,
54981
55129
  ...maxObjectPackBytes ? {
54982
55130
  maxObjectPackBytes
54983
55131
  } : {},
@@ -55020,10 +55168,26 @@ function syncCommand() {
55020
55168
  if (!tenantHint) {
55021
55169
  throw new CliUserError("no active tenant. Run `prosa auth use <tenant>` first.");
55022
55170
  }
55171
+ const uploadConcurrency = new AdaptiveUploadConcurrencyController(options.objectConcurrency, (change) => {
55172
+ if (!options.verbose) return;
55173
+ const direction = change.reason === "retry" ? "reduced" : "increased";
55174
+ process.stderr.write(`adaptive object concurrency ${direction} ${change.previous}->${change.current} after ${change.reason}
55175
+ `);
55176
+ });
55023
55177
  const client = new ProsaApiClient({
55024
55178
  baseUrl: server,
55025
55179
  token: entry.token,
55026
- tenantId: tenantHint
55180
+ tenantId: tenantHint,
55181
+ onRetry: /* @__PURE__ */ __name((event) => {
55182
+ if (event.operation.startsWith("object ")) uploadConcurrency.recordRetry();
55183
+ if (options.verbose) {
55184
+ process.stderr.write(`retry ${event.operation} attempt=${event.attempt}/${event.maxAttempts} delayMs=${event.delayMs} reason=${event.reason}
55185
+ `);
55186
+ }
55187
+ }, "onRetry"),
55188
+ onRequestSuccess: /* @__PURE__ */ __name((event) => {
55189
+ if (event.operation.startsWith("object ")) uploadConcurrency.recordSuccess();
55190
+ }, "onRequestSuccess")
55027
55191
  });
55028
55192
  const storePath = path12.resolve(options.store ?? defaultBundlePath13());
55029
55193
  const exists = await bundleManifestExists(storePath);
@@ -55115,6 +55279,7 @@ function syncCommand() {
55115
55279
  maxRowsPerCommit: handshake.limits.maxRowsPerCommit,
55116
55280
  maxObjectPackBytes: handshake.limits.maxObjectBytes,
55117
55281
  objectConcurrency: options.objectConcurrency,
55282
+ uploadConcurrency,
55118
55283
  batchConcurrency: options.batchConcurrency,
55119
55284
  verbose: options.verbose,
55120
55285
  progress,