@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/cli/main.js CHANGED
@@ -28394,8 +28394,9 @@ var CliUserError = class extends Error {
28394
28394
  import { request as httpRequest } from "http";
28395
28395
  import { request as httpsRequest } from "https";
28396
28396
  import { OBJECT_PACK_BINARY_CONTENT_TYPE, encodeBinaryObjectPack } from "@c3-oss/prosa-sync";
28397
- var OBJECT_UPLOAD_MAX_ATTEMPTS = 4;
28398
- var OBJECT_UPLOAD_BASE_BACKOFF_MS = 250;
28397
+ var OBJECT_UPLOAD_MAX_ATTEMPTS = 6;
28398
+ var OBJECT_UPLOAD_BASE_BACKOFF_MS = 500;
28399
+ var OBJECT_UPLOAD_MAX_BACKOFF_MS = 15e3;
28399
28400
  function trimTrailingSlash(url) {
28400
28401
  return url.endsWith("/") ? url.slice(0, -1) : url;
28401
28402
  }
@@ -28420,7 +28421,8 @@ __name(retryAfterMs, "retryAfterMs");
28420
28421
  function objectUploadBackoffMs(attempt, headers) {
28421
28422
  const retryAfter = headers ? retryAfterMs(headers) : void 0;
28422
28423
  if (retryAfter !== void 0) return retryAfter;
28423
- return OBJECT_UPLOAD_BASE_BACKOFF_MS * 2 ** attempt;
28424
+ const exponential = Math.min(OBJECT_UPLOAD_MAX_BACKOFF_MS, OBJECT_UPLOAD_BASE_BACKOFF_MS * 2 ** attempt);
28425
+ return Math.floor(exponential + Math.random() * Math.min(250, exponential));
28424
28426
  }
28425
28427
  __name(objectUploadBackoffMs, "objectUploadBackoffMs");
28426
28428
  function isRetryableObjectUploadStatus(status) {
@@ -28434,10 +28436,21 @@ function isRetryableNetworkError(err) {
28434
28436
  return code != null && [
28435
28437
  "ECONNRESET",
28436
28438
  "ETIMEDOUT",
28437
- "EPIPE"
28439
+ "EPIPE",
28440
+ "ECONNREFUSED",
28441
+ "UND_ERR_SOCKET",
28442
+ "UND_ERR_CONNECT_TIMEOUT"
28438
28443
  ].includes(code);
28439
28444
  }
28440
28445
  __name(isRetryableNetworkError, "isRetryableNetworkError");
28446
+ function networkErrorReason(err) {
28447
+ if (err instanceof Error) {
28448
+ const code = err.code ?? err.cause?.code;
28449
+ return code ? `${err.message} (${code})` : err.message;
28450
+ }
28451
+ return String(err);
28452
+ }
28453
+ __name(networkErrorReason, "networkErrorReason");
28441
28454
  function isUnsupportedBinaryObjectPackResponse(status, text) {
28442
28455
  return status === 415 || status === 400 && /Unsupported Media Type|JSON body required/i.test(text);
28443
28456
  }
@@ -28535,12 +28548,16 @@ var ProsaApiClient = class {
28535
28548
  baseUrl;
28536
28549
  fetchFn;
28537
28550
  hasInjectedFetch;
28551
+ onRetry;
28552
+ onRequestSuccess;
28538
28553
  token;
28539
28554
  tenantId;
28540
28555
  constructor(opts) {
28541
28556
  this.baseUrl = trimTrailingSlash(opts.baseUrl);
28542
28557
  this.fetchFn = opts.fetch ?? globalThis.fetch;
28543
28558
  this.hasInjectedFetch = Boolean(opts.fetch);
28559
+ this.onRetry = opts.onRetry;
28560
+ this.onRequestSuccess = opts.onRequestSuccess;
28544
28561
  this.token = opts.token;
28545
28562
  this.tenantId = opts.tenantId;
28546
28563
  }
@@ -28571,6 +28588,90 @@ var ProsaApiClient = class {
28571
28588
  });
28572
28589
  return this.parseTrpc(path14, response);
28573
28590
  }
28591
+ async trpcMutationRetriable(path14, input, opts) {
28592
+ return this.retriableFetch({
28593
+ operation: opts.operation,
28594
+ retryHttpStatusBeforeParse: false,
28595
+ retryParseErrorsOnRetryableStatus: true,
28596
+ retryStructuredErrorsOnRetryableStatus: opts.retryStructuredErrorsOnRetryableStatus,
28597
+ request: /* @__PURE__ */ __name(() => this.fetchFn(`${this.baseUrl}/trpc/${path14}`, {
28598
+ method: "POST",
28599
+ headers: this.headers({
28600
+ "content-type": "application/json",
28601
+ ...opts.headers
28602
+ }),
28603
+ body: JSON.stringify(input ?? {})
28604
+ }), "request"),
28605
+ parse: /* @__PURE__ */ __name((response) => this.parseTrpc(path14, response), "parse")
28606
+ });
28607
+ }
28608
+ async retriableFetch(opts) {
28609
+ const { operation: operation2, request: request2, parse: parse3 } = opts;
28610
+ let lastError;
28611
+ for (let attempt = 0; attempt < OBJECT_UPLOAD_MAX_ATTEMPTS; attempt += 1) {
28612
+ let response;
28613
+ try {
28614
+ response = await request2();
28615
+ } catch (err) {
28616
+ lastError = err;
28617
+ if (!isRetryableNetworkError(err) || attempt >= OBJECT_UPLOAD_MAX_ATTEMPTS - 1) {
28618
+ throw this.wrapRetriedError(operation2, attempt + 1, err);
28619
+ }
28620
+ const delayMs = objectUploadBackoffMs(attempt);
28621
+ this.onRetry?.({
28622
+ operation: operation2,
28623
+ attempt: attempt + 1,
28624
+ maxAttempts: OBJECT_UPLOAD_MAX_ATTEMPTS,
28625
+ delayMs,
28626
+ reason: networkErrorReason(err)
28627
+ });
28628
+ await sleep(delayMs);
28629
+ continue;
28630
+ }
28631
+ if (opts.retryHttpStatusBeforeParse !== false && isRetryableObjectUploadStatus(response.status) && attempt < OBJECT_UPLOAD_MAX_ATTEMPTS - 1) {
28632
+ const delayMs = objectUploadBackoffMs(attempt, response.headers);
28633
+ this.onRetry?.({
28634
+ operation: operation2,
28635
+ attempt: attempt + 1,
28636
+ maxAttempts: OBJECT_UPLOAD_MAX_ATTEMPTS,
28637
+ delayMs,
28638
+ reason: `HTTP ${response.status}`
28639
+ });
28640
+ await response.arrayBuffer().catch(() => void 0);
28641
+ await sleep(delayMs);
28642
+ continue;
28643
+ }
28644
+ try {
28645
+ const parsed = await parse3(response);
28646
+ this.onRequestSuccess?.({
28647
+ operation: operation2,
28648
+ attempts: attempt + 1
28649
+ });
28650
+ return parsed;
28651
+ } catch (err) {
28652
+ if (opts.retryParseErrorsOnRetryableStatus && (opts.retryStructuredErrorsOnRetryableStatus !== false || !(err instanceof ProsaApiError)) && isRetryableObjectUploadStatus(response.status) && attempt < OBJECT_UPLOAD_MAX_ATTEMPTS - 1) {
28653
+ const delayMs = objectUploadBackoffMs(attempt, response.headers);
28654
+ this.onRetry?.({
28655
+ operation: operation2,
28656
+ attempt: attempt + 1,
28657
+ maxAttempts: OBJECT_UPLOAD_MAX_ATTEMPTS,
28658
+ delayMs,
28659
+ reason: `HTTP ${response.status}: ${networkErrorReason(err)}`
28660
+ });
28661
+ await sleep(delayMs);
28662
+ continue;
28663
+ }
28664
+ throw this.wrapRetriedError(operation2, attempt + 1, err);
28665
+ }
28666
+ }
28667
+ throw this.wrapRetriedError(operation2, OBJECT_UPLOAD_MAX_ATTEMPTS, lastError);
28668
+ }
28669
+ wrapRetriedError(operation2, attempts, err) {
28670
+ if (err instanceof ProsaApiError) return err;
28671
+ if (attempts <= 1) return err instanceof Error ? err : new Error(String(err));
28672
+ const reason = networkErrorReason(err);
28673
+ return new CliUserError(`${operation2} failed after ${attempts} attempts: ${reason}`);
28674
+ }
28574
28675
  async parseTrpc(path14, response) {
28575
28676
  const text = await response.text();
28576
28677
  if (!text) throw new CliUserError(`${path14}: empty response (status ${response.status})`);
@@ -28680,14 +28781,18 @@ var ProsaApiClient = class {
28680
28781
  return this.trpcMutation("sync.planUpload", input);
28681
28782
  }
28682
28783
  async syncCommitUpload(input, opts = {}) {
28683
- return this.trpcMutation("sync.commitUpload", input, {
28784
+ return this.trpcMutationRetriable("sync.commitUpload", input, {
28785
+ operation: "sync.commitUpload",
28684
28786
  headers: opts.idempotencyKey ? {
28685
28787
  "idempotency-key": opts.idempotencyKey
28686
- } : void 0
28788
+ } : void 0,
28789
+ retryStructuredErrorsOnRetryableStatus: false
28687
28790
  });
28688
28791
  }
28689
28792
  async syncVerifyPromotion(input) {
28690
- return this.trpcMutation("sync.verifyPromotion", input);
28793
+ return this.trpcMutationRetriable("sync.verifyPromotion", input, {
28794
+ operation: "sync.verifyPromotion"
28795
+ });
28691
28796
  }
28692
28797
  async syncAckCleanup(input) {
28693
28798
  return this.trpcMutation("sync.ackCleanup", input);
@@ -28698,21 +28803,9 @@ var ProsaApiClient = class {
28698
28803
  } : void 0);
28699
28804
  }
28700
28805
  async uploadObjectBytes(input) {
28701
- let lastError;
28702
- for (let attempt = 0; attempt < OBJECT_UPLOAD_MAX_ATTEMPTS; attempt += 1) {
28703
- try {
28704
- return await this.uploadObjectBytesOnce(input, attempt);
28705
- } catch (err) {
28706
- lastError = err;
28707
- if (!isRetryableNetworkError(err) || attempt >= OBJECT_UPLOAD_MAX_ATTEMPTS - 1) {
28708
- throw err;
28709
- }
28710
- await sleep(objectUploadBackoffMs(attempt));
28711
- }
28712
- }
28713
- throw lastError instanceof Error ? lastError : new CliUserError("object upload failed");
28806
+ return this.uploadObjectBytesOnce(input);
28714
28807
  }
28715
- async uploadObjectBytesOnce(input, attempt) {
28808
+ async uploadObjectBytesOnce(input) {
28716
28809
  const url = new URL(`${this.baseUrl}/objects/${input.objectId}`);
28717
28810
  url.searchParams.set("batchId", input.batchId);
28718
28811
  url.searchParams.set("hash", input.hash);
@@ -28720,55 +28813,65 @@ var ProsaApiClient = class {
28720
28813
  url.searchParams.set("uncompressed", String(input.uncompressedSize));
28721
28814
  url.searchParams.set("compression", input.compression ?? "zstd");
28722
28815
  if (input.transportHash) url.searchParams.set("transportHash", input.transportHash);
28723
- const response = await this.fetchFn(url.toString(), {
28724
- method: "PUT",
28725
- headers: this.headers({
28726
- "content-type": "application/octet-stream"
28727
- }),
28728
- body: input.bytes
28816
+ return this.retriableFetch({
28817
+ operation: "object PUT upload",
28818
+ request: /* @__PURE__ */ __name(() => this.fetchFn(url.toString(), {
28819
+ method: "PUT",
28820
+ headers: this.headers({
28821
+ "content-type": "application/octet-stream"
28822
+ }),
28823
+ body: input.bytes
28824
+ }), "request"),
28825
+ parse: /* @__PURE__ */ __name(async (response) => {
28826
+ const text = await response.text();
28827
+ if (response.status >= 400) {
28828
+ throw new CliUserError(`object upload failed: ${response.status} ${text}`);
28829
+ }
28830
+ const parsed = JSON.parse(text);
28831
+ return {
28832
+ alreadyExisted: Boolean(parsed.alreadyExisted)
28833
+ };
28834
+ }, "parse")
28729
28835
  });
28730
- const text = await response.text();
28731
- if (response.status >= 400) {
28732
- if (isRetryableObjectUploadStatus(response.status) && attempt < OBJECT_UPLOAD_MAX_ATTEMPTS - 1) {
28733
- await sleep(objectUploadBackoffMs(attempt, response.headers));
28734
- return this.uploadObjectBytesOnce(input, attempt + 1);
28735
- }
28736
- throw new CliUserError(`object upload failed: ${response.status} ${text}`);
28737
- }
28738
- const parsed = JSON.parse(text);
28739
- return {
28740
- alreadyExisted: Boolean(parsed.alreadyExisted)
28741
- };
28742
28836
  }
28743
28837
  async uploadObjectPack(input) {
28744
28838
  const url = new URL(`${this.baseUrl}/object-packs`);
28745
28839
  url.searchParams.set("batchId", input.batchId);
28746
28840
  const prepared = this.prepareObjectPackUpload(input.objects);
28747
- const binaryResponse = await this.fetchFn(url.toString(), {
28748
- method: "POST",
28749
- headers: this.headers({
28750
- "content-type": OBJECT_PACK_BINARY_CONTENT_TYPE
28751
- }),
28752
- body: encodeBinaryObjectPack({
28753
- entries: prepared.entries,
28754
- payload: prepared.payload
28755
- })
28841
+ const binary = await this.retriableFetch({
28842
+ operation: "object pack binary upload",
28843
+ request: /* @__PURE__ */ __name(() => this.fetchFn(url.toString(), {
28844
+ method: "POST",
28845
+ headers: this.headers({
28846
+ "content-type": OBJECT_PACK_BINARY_CONTENT_TYPE
28847
+ }),
28848
+ body: encodeBinaryObjectPack({
28849
+ entries: prepared.entries,
28850
+ payload: prepared.payload
28851
+ })
28852
+ }), "request"),
28853
+ parse: /* @__PURE__ */ __name(async (response) => ({
28854
+ status: response.status,
28855
+ text: await response.text()
28856
+ }), "parse")
28756
28857
  });
28757
- const binaryText = await binaryResponse.text();
28758
- if (!isUnsupportedBinaryObjectPackResponse(binaryResponse.status, binaryText)) {
28759
- return this.parseObjectPackUploadResponse(binaryResponse.status, binaryText);
28760
- }
28761
- const fallbackResponse = await this.fetchFn(url.toString(), {
28762
- method: "POST",
28763
- headers: this.headers({
28764
- "content-type": "application/json"
28765
- }),
28766
- body: JSON.stringify({
28767
- bytesBase64: prepared.payload.toString("base64"),
28768
- entries: prepared.entries
28769
- })
28858
+ if (!isUnsupportedBinaryObjectPackResponse(binary.status, binary.text)) {
28859
+ return this.parseObjectPackUploadResponse(binary.status, binary.text);
28860
+ }
28861
+ return this.retriableFetch({
28862
+ operation: "object pack JSON upload",
28863
+ request: /* @__PURE__ */ __name(() => this.fetchFn(url.toString(), {
28864
+ method: "POST",
28865
+ headers: this.headers({
28866
+ "content-type": "application/json"
28867
+ }),
28868
+ body: JSON.stringify({
28869
+ bytesBase64: prepared.payload.toString("base64"),
28870
+ entries: prepared.entries
28871
+ })
28872
+ }), "request"),
28873
+ parse: /* @__PURE__ */ __name(async (response) => this.parseObjectPackUploadResponse(response.status, await response.text()), "parse")
28770
28874
  });
28771
- return this.parseObjectPackUploadResponse(fallbackResponse.status, await fallbackResponse.text());
28772
28875
  }
28773
28876
  prepareObjectPackUpload(objects) {
28774
28877
  let offset = 0;
@@ -53667,6 +53770,46 @@ __name(runReadUploadPipeline, "runReadUploadPipeline");
53667
53770
  // src/cli/sync/promotion.ts
53668
53771
  import { rm as rm4 } from "fs/promises";
53669
53772
  import path11 from "path";
53773
+ var AdaptiveUploadConcurrencyController = class {
53774
+ static {
53775
+ __name(this, "AdaptiveUploadConcurrencyController");
53776
+ }
53777
+ ceiling;
53778
+ onChange;
53779
+ value;
53780
+ successStreak = 0;
53781
+ constructor(ceiling, onChange) {
53782
+ this.ceiling = ceiling;
53783
+ this.onChange = onChange;
53784
+ this.value = Math.max(1, ceiling);
53785
+ }
53786
+ current() {
53787
+ return this.value;
53788
+ }
53789
+ recordRetry() {
53790
+ const previous = this.value;
53791
+ this.value = Math.max(1, Math.floor(this.value / 2));
53792
+ this.successStreak = 0;
53793
+ if (this.value !== previous) this.onChange?.({
53794
+ previous,
53795
+ current: this.value,
53796
+ reason: "retry"
53797
+ });
53798
+ }
53799
+ recordSuccess() {
53800
+ if (this.value >= this.ceiling) return;
53801
+ this.successStreak += 1;
53802
+ if (this.successStreak < 10) return;
53803
+ this.successStreak = 0;
53804
+ const previous = this.value;
53805
+ this.value = Math.min(this.ceiling, this.value + 1);
53806
+ if (this.value !== previous) this.onChange?.({
53807
+ previous,
53808
+ current: this.value,
53809
+ reason: "success"
53810
+ });
53811
+ }
53812
+ };
53670
53813
  var BLAKE3_HEX_RE = /^[0-9a-f]{64}$/i;
53671
53814
  var OBJECT_PACK_ENTRY_LIMIT = 1024;
53672
53815
  var DEFAULT_OBJECT_PACK_MAX_BYTES = 8 * 1024 * 1024;
@@ -53729,9 +53872,10 @@ async function uploadObjectPut(client, batchId, object) {
53729
53872
  });
53730
53873
  }
53731
53874
  __name(uploadObjectPut, "uploadObjectPut");
53732
- async function uploadMissingCasObjects({ client, batchId, missingObjects, objectConcurrency, maxObjectPackBytes = DEFAULT_OBJECT_PACK_MAX_BYTES }) {
53875
+ async function uploadMissingCasObjects({ client, batchId, missingObjects, objectConcurrency, uploadConcurrency, maxObjectPackBytes = DEFAULT_OBJECT_PACK_MAX_BYTES }) {
53733
53876
  const { packs, putObjects } = splitMissingObjectUploads(missingObjects, maxObjectPackBytes);
53734
- await mapConcurrent(packs, objectConcurrency, async (pack) => {
53877
+ const concurrency = /* @__PURE__ */ __name(() => uploadConcurrency?.current() ?? objectConcurrency, "concurrency");
53878
+ await mapConcurrent(packs, concurrency(), async (pack) => {
53735
53879
  await client.uploadObjectPack({
53736
53880
  batchId,
53737
53881
  objects: pack.map(({ entry, bytes }) => ({
@@ -53740,7 +53884,7 @@ async function uploadMissingCasObjects({ client, batchId, missingObjects, object
53740
53884
  }))
53741
53885
  });
53742
53886
  });
53743
- await mapConcurrent(putObjects, objectConcurrency, async (object) => {
53887
+ await mapConcurrent(putObjects, concurrency(), async (object) => {
53744
53888
  await uploadObjectPut(client, batchId, object);
53745
53889
  });
53746
53890
  return {
@@ -54012,6 +54156,7 @@ async function uploadMissingCasObjectsWithReadPipeline(opts) {
54012
54156
  batchId: opts.batchId,
54013
54157
  missingObjects: batch,
54014
54158
  objectConcurrency: opts.objectConcurrency,
54159
+ uploadConcurrency: opts.uploadConcurrency,
54015
54160
  ...opts.maxObjectPackBytes ? {
54016
54161
  maxObjectPackBytes: opts.maxObjectPackBytes
54017
54162
  } : {}
@@ -54721,7 +54866,7 @@ function readArtifactChunk(bundle, afterId, limit) {
54721
54866
  };
54722
54867
  }
54723
54868
  __name(readArtifactChunk, "readArtifactChunk");
54724
- async function promoteChunk({ client, deviceId, storePath, casObjects, projection, label, metrics, objectConcurrency, maxObjectPackBytes, verbose }) {
54869
+ async function promoteChunk({ client, deviceId, storePath, casObjects, projection, label, metrics, objectConcurrency, uploadConcurrency, maxObjectPackBytes, verbose }) {
54725
54870
  const objectEntries = casObjects.map((c6) => c6.entry);
54726
54871
  const totalStart = Date.now();
54727
54872
  const planStart = Date.now();
@@ -54747,6 +54892,7 @@ async function promoteChunk({ client, deviceId, storePath, casObjects, projectio
54747
54892
  storePath,
54748
54893
  missingObjects,
54749
54894
  objectConcurrency,
54895
+ uploadConcurrency,
54750
54896
  ...maxObjectPackBytes ? {
54751
54897
  maxObjectPackBytes
54752
54898
  } : {},
@@ -54858,7 +55004,7 @@ async function promotePhase(tasks, concurrency, worker) {
54858
55004
  return results;
54859
55005
  }
54860
55006
  __name(promotePhase, "promotePhase");
54861
- async function promoteChunkedUpload({ client, deviceId, storePath, bundle, maxObjectsPerPlan, maxRowsPerCommit, maxObjectPackBytes, objectConcurrency, batchConcurrency, verbose, progress, totalBatches, checkpoint }) {
55007
+ async function promoteChunkedUpload({ client, deviceId, storePath, bundle, maxObjectsPerPlan, maxRowsPerCommit, maxObjectPackBytes, objectConcurrency, uploadConcurrency, batchConcurrency, verbose, progress, totalBatches, checkpoint }) {
54862
55008
  let batchCount = 0;
54863
55009
  let lastReceipt = null;
54864
55010
  let metrics = emptySyncMetrics(objectConcurrency);
@@ -54883,6 +55029,7 @@ async function promoteChunkedUpload({ client, deviceId, storePath, bundle, maxOb
54883
55029
  projection: toProjection(chunk.rows),
54884
55030
  label: `${label} batch ${phaseStart + cursor.sequence}`,
54885
55031
  objectConcurrency,
55032
+ uploadConcurrency,
54886
55033
  ...maxObjectPackBytes ? {
54887
55034
  maxObjectPackBytes
54888
55035
  } : {},
@@ -54974,6 +55121,7 @@ async function promoteChunkedUpload({ client, deviceId, storePath, bundle, maxOb
54974
55121
  label: `chunk ${batchCount}`,
54975
55122
  metrics,
54976
55123
  objectConcurrency,
55124
+ uploadConcurrency,
54977
55125
  ...maxObjectPackBytes ? {
54978
55126
  maxObjectPackBytes
54979
55127
  } : {},
@@ -55016,10 +55164,26 @@ function syncCommand() {
55016
55164
  if (!tenantHint) {
55017
55165
  throw new CliUserError("no active tenant. Run `prosa auth use <tenant>` first.");
55018
55166
  }
55167
+ const uploadConcurrency = new AdaptiveUploadConcurrencyController(options.objectConcurrency, (change) => {
55168
+ if (!options.verbose) return;
55169
+ const direction = change.reason === "retry" ? "reduced" : "increased";
55170
+ process.stderr.write(`adaptive object concurrency ${direction} ${change.previous}->${change.current} after ${change.reason}
55171
+ `);
55172
+ });
55019
55173
  const client = new ProsaApiClient({
55020
55174
  baseUrl: server,
55021
55175
  token: entry.token,
55022
- tenantId: tenantHint
55176
+ tenantId: tenantHint,
55177
+ onRetry: /* @__PURE__ */ __name((event) => {
55178
+ if (event.operation.startsWith("object ")) uploadConcurrency.recordRetry();
55179
+ if (options.verbose) {
55180
+ process.stderr.write(`retry ${event.operation} attempt=${event.attempt}/${event.maxAttempts} delayMs=${event.delayMs} reason=${event.reason}
55181
+ `);
55182
+ }
55183
+ }, "onRetry"),
55184
+ onRequestSuccess: /* @__PURE__ */ __name((event) => {
55185
+ if (event.operation.startsWith("object ")) uploadConcurrency.recordSuccess();
55186
+ }, "onRequestSuccess")
55023
55187
  });
55024
55188
  const storePath = path12.resolve(options.store ?? defaultBundlePath13());
55025
55189
  const exists = await bundleManifestExists(storePath);
@@ -55111,6 +55275,7 @@ function syncCommand() {
55111
55275
  maxRowsPerCommit: handshake.limits.maxRowsPerCommit,
55112
55276
  maxObjectPackBytes: handshake.limits.maxObjectBytes,
55113
55277
  objectConcurrency: options.objectConcurrency,
55278
+ uploadConcurrency,
55114
55279
  batchConcurrency: options.batchConcurrency,
55115
55280
  verbose: options.verbose,
55116
55281
  progress,