@powerhousedao/reactor 6.0.0-dev.170 → 6.0.0-dev.172

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.js CHANGED
@@ -570,10 +570,8 @@ var ReactorClient = class {
570
570
  limit: paging.limit
571
571
  };
572
572
  const scopeResult = (await this.reactor.getOperations(documentId, scopeView, filter, scopePaging, void 0, signal))[scopeName];
573
- if (scopeResult) {
574
- allOperations.push(...scopeResult.results);
575
- if (scopeResult.nextCursor) activeCursors[scopeName] = scopeResult.nextCursor;
576
- }
573
+ allOperations.push(...scopeResult.results);
574
+ if (scopeResult.nextCursor) activeCursors[scopeName] = scopeResult.nextCursor;
577
575
  }
578
576
  allOperations.sort((a, b) => a.index - b.index);
579
577
  return {
@@ -2724,6 +2722,17 @@ var SimpleJobExecutorManager = class {
2724
2722
  }
2725
2723
  };
2726
2724
  //#endregion
2725
+ //#region src/shared/utils.ts
2726
+ function matchesScope(view = {}, scope) {
2727
+ if (view.scopes) return view.scopes.includes(scope);
2728
+ return true;
2729
+ }
2730
+ function yieldToMain() {
2731
+ const s = globalThis.scheduler;
2732
+ if (s?.yield) return s.yield();
2733
+ return new Promise((resolve) => setTimeout(resolve, 0));
2734
+ }
2735
+ //#endregion
2727
2736
  //#region src/utils/reshuffle.ts
2728
2737
  const STRICT_ORDER_ACTION_TYPES = new Set([
2729
2738
  "CREATE_DOCUMENT",
@@ -3143,7 +3152,8 @@ var SimpleJobExecutor = class {
3143
3152
  maxConcurrency: config.maxConcurrency ?? 1,
3144
3153
  jobTimeoutMs: config.jobTimeoutMs ?? 3e4,
3145
3154
  retryBaseDelayMs: config.retryBaseDelayMs ?? 100,
3146
- retryMaxDelayMs: config.retryMaxDelayMs ?? 5e3
3155
+ retryMaxDelayMs: config.retryMaxDelayMs ?? 5e3,
3156
+ yieldDeadlineMs: config.yieldDeadlineMs ?? 50
3147
3157
  };
3148
3158
  this.signatureVerifierModule = new SignatureVerifier(signatureVerifier);
3149
3159
  this.documentActionHandler = new DocumentActionHandler(registry, logger);
@@ -3247,6 +3257,7 @@ var SimpleJobExecutor = class {
3247
3257
  operationsWithContext,
3248
3258
  error: /* @__PURE__ */ new Error(`Invalid timestamp "${action.timestampUtcMs}" on action ${action.type} (id: ${action.id})`)
3249
3259
  };
3260
+ let lastYield = performance.now();
3250
3261
  for (let actionIndex = 0; actionIndex < actions.length; actionIndex++) {
3251
3262
  const action = actions[actionIndex];
3252
3263
  const skip = skipValues?.[actionIndex] ?? 0;
@@ -3259,6 +3270,16 @@ var SimpleJobExecutor = class {
3259
3270
  operationsWithContext,
3260
3271
  error: error.error
3261
3272
  };
3273
+ if (performance.now() - lastYield > this.config.yieldDeadlineMs) {
3274
+ await yieldToMain();
3275
+ lastYield = performance.now();
3276
+ if (signal?.aborted) return {
3277
+ success: false,
3278
+ generatedOperations,
3279
+ operationsWithContext,
3280
+ error: /* @__PURE__ */ new Error("Aborted")
3281
+ };
3282
+ }
3262
3283
  }
3263
3284
  return {
3264
3285
  success: true,
@@ -6537,6 +6558,113 @@ function mergeCollectionMemberships(events) {
6537
6558
  }
6538
6559
  return mergedMemberships;
6539
6560
  }
6561
+ /**
6562
+ * Chunks sync operations into batches that respect dependency-connected
6563
+ * components. SyncOps linked by jobDependencies are kept in the same chunk.
6564
+ * If a connected component exceeds maxSize, it is split by topological order.
6565
+ */
6566
+ function chunkSyncOperations(items, maxSize) {
6567
+ if (items.length === 0) return [];
6568
+ if (items.length <= maxSize) return [items];
6569
+ const parent = items.map((_, i) => i);
6570
+ const rank = new Array(items.length).fill(0);
6571
+ function find(x) {
6572
+ while (parent[x] !== x) {
6573
+ parent[x] = parent[parent[x]];
6574
+ x = parent[x];
6575
+ }
6576
+ return x;
6577
+ }
6578
+ function union(a, b) {
6579
+ const ra = find(a);
6580
+ const rb = find(b);
6581
+ if (ra === rb) return;
6582
+ if (rank[ra] < rank[rb]) parent[ra] = rb;
6583
+ else if (rank[ra] > rank[rb]) parent[rb] = ra;
6584
+ else {
6585
+ parent[rb] = ra;
6586
+ rank[ra]++;
6587
+ }
6588
+ }
6589
+ const jobIdToIndex = /* @__PURE__ */ new Map();
6590
+ for (let i = 0; i < items.length; i++) jobIdToIndex.set(items[i].syncOp.jobId, i);
6591
+ for (let i = 0; i < items.length; i++) for (const dep of items[i].syncOp.jobDependencies) {
6592
+ const depIdx = jobIdToIndex.get(dep);
6593
+ if (depIdx !== void 0) union(i, depIdx);
6594
+ }
6595
+ const componentMap = /* @__PURE__ */ new Map();
6596
+ for (let i = 0; i < items.length; i++) {
6597
+ const root = find(i);
6598
+ let component = componentMap.get(root);
6599
+ if (!component) {
6600
+ component = [];
6601
+ componentMap.set(root, component);
6602
+ }
6603
+ component.push(items[i]);
6604
+ }
6605
+ const components = [...componentMap.values()];
6606
+ const chunks = [];
6607
+ let currentChunk = [];
6608
+ for (const component of components) {
6609
+ if (component.length > maxSize) {
6610
+ if (currentChunk.length > 0) {
6611
+ chunks.push(currentChunk);
6612
+ currentChunk = [];
6613
+ }
6614
+ for (const subChunk of splitComponent(component, maxSize)) chunks.push(subChunk);
6615
+ continue;
6616
+ }
6617
+ if (currentChunk.length + component.length > maxSize) {
6618
+ if (currentChunk.length > 0) chunks.push(currentChunk);
6619
+ currentChunk = [...component];
6620
+ } else currentChunk.push(...component);
6621
+ }
6622
+ if (currentChunk.length > 0) chunks.push(currentChunk);
6623
+ return chunks;
6624
+ }
6625
+ /**
6626
+ * Splits an oversized connected component into chunks by topological order.
6627
+ * Cross-chunk dependency references are handled by the caller's dep filter.
6628
+ */
6629
+ function splitComponent(items, maxSize) {
6630
+ const jobIdToItem = /* @__PURE__ */ new Map();
6631
+ const jobIds = /* @__PURE__ */ new Set();
6632
+ for (const item of items) {
6633
+ jobIdToItem.set(item.syncOp.jobId, item);
6634
+ jobIds.add(item.syncOp.jobId);
6635
+ }
6636
+ const inDegree = /* @__PURE__ */ new Map();
6637
+ const adjacency = /* @__PURE__ */ new Map();
6638
+ for (const item of items) {
6639
+ const key = item.syncOp.jobId;
6640
+ if (!inDegree.has(key)) inDegree.set(key, 0);
6641
+ if (!adjacency.has(key)) adjacency.set(key, []);
6642
+ for (const dep of item.syncOp.jobDependencies) if (jobIds.has(dep)) {
6643
+ inDegree.set(key, (inDegree.get(key) ?? 0) + 1);
6644
+ if (!adjacency.has(dep)) adjacency.set(dep, []);
6645
+ adjacency.get(dep).push(key);
6646
+ }
6647
+ }
6648
+ const queue = [];
6649
+ for (const [key, degree] of inDegree) if (degree === 0) queue.push(key);
6650
+ const sorted = [];
6651
+ while (queue.length > 0) {
6652
+ const key = queue.shift();
6653
+ sorted.push(jobIdToItem.get(key));
6654
+ for (const neighbor of adjacency.get(key) ?? []) {
6655
+ const newDegree = (inDegree.get(neighbor) ?? 1) - 1;
6656
+ inDegree.set(neighbor, newDegree);
6657
+ if (newDegree === 0) queue.push(neighbor);
6658
+ }
6659
+ }
6660
+ if (sorted.length < items.length) {
6661
+ const sortedIds = new Set(sorted.map((item) => item.syncOp.jobId));
6662
+ for (const item of items) if (!sortedIds.has(item.syncOp.jobId)) sorted.push(item);
6663
+ }
6664
+ const chunks = [];
6665
+ for (let i = 0; i < sorted.length; i += maxSize) chunks.push(sorted.slice(i, i + maxSize));
6666
+ return chunks;
6667
+ }
6540
6668
  //#endregion
6541
6669
  //#region src/sync/channels/interval-poll-timer.ts
6542
6670
  const DEFAULT_CONFIG = {
@@ -6788,6 +6916,7 @@ var GqlRequestChannel = class {
6788
6916
  pushBlocked = false;
6789
6917
  isPushing = false;
6790
6918
  pendingDrain = false;
6919
+ receivingPages = false;
6791
6920
  connectionState = "connecting";
6792
6921
  connectionStateCallbacks = /* @__PURE__ */ new Set();
6793
6922
  constructor(logger, channelId, remoteName, cursorStorage, config, operationIndex, pollTimer) {
@@ -6822,6 +6951,10 @@ var GqlRequestChannel = class {
6822
6951
  return;
6823
6952
  }
6824
6953
  if (this.pushBlocked) return;
6954
+ if (this.receivingPages) {
6955
+ this.pendingDrain = true;
6956
+ return;
6957
+ }
6825
6958
  this.attemptPush(syncOps);
6826
6959
  });
6827
6960
  this.outbox.onRemoved((syncOps) => {
@@ -6875,7 +7008,8 @@ var GqlRequestChannel = class {
6875
7008
  lastSuccessUtcMs: this.lastSuccessUtcMs ?? 0,
6876
7009
  lastFailureUtcMs: this.lastFailureUtcMs ?? 0,
6877
7010
  pushBlocked: this.pushBlocked,
6878
- pushFailureCount: this.pushFailureCount
7011
+ pushFailureCount: this.pushFailureCount,
7012
+ receivingPages: this.receivingPages
6879
7013
  };
6880
7014
  }
6881
7015
  onConnectionStateChange(callback) {
@@ -6923,7 +7057,7 @@ var GqlRequestChannel = class {
6923
7057
  if (!this.handlePollError(error)) throw error;
6924
7058
  return;
6925
7059
  }
6926
- const { envelopes, ackOrdinal, deadLetters } = response;
7060
+ const { envelopes, ackOrdinal, deadLetters, hasMore } = response;
6927
7061
  if (ackOrdinal > 0) trimMailboxFromAckOrdinal(this.outbox, ackOrdinal);
6928
7062
  const sortedEnvelopes = sortEnvelopesByFirstOperationTimestamp(envelopes);
6929
7063
  const allSyncOps = [];
@@ -6935,6 +7069,11 @@ var GqlRequestChannel = class {
6935
7069
  const consolidated = allSyncOps.length > 1 ? consolidateSyncOperations(allSyncOps) : allSyncOps;
6936
7070
  if (consolidated.length > 0) this.inbox.add(...consolidated);
6937
7071
  if (deadLetters.length > 0) this.handleRemoteDeadLetters(deadLetters);
7072
+ if (hasMore) this.receivingPages = true;
7073
+ else if (this.receivingPages) {
7074
+ this.receivingPages = false;
7075
+ this.drainOutbox();
7076
+ }
6938
7077
  this.lastSuccessUtcMs = Date.now();
6939
7078
  this.failureCount = 0;
6940
7079
  this.transitionConnectionState("connected");
@@ -7084,6 +7223,7 @@ var GqlRequestChannel = class {
7084
7223
  scopes
7085
7224
  operationCount
7086
7225
  }
7226
+ hasMore
7087
7227
  }
7088
7228
  }
7089
7229
  `;
@@ -7096,7 +7236,8 @@ var GqlRequestChannel = class {
7096
7236
  return {
7097
7237
  envelopes: response.pollSyncEnvelopes.envelopes,
7098
7238
  ackOrdinal: response.pollSyncEnvelopes.ackOrdinal,
7099
- deadLetters: response.pollSyncEnvelopes.deadLetters ?? []
7239
+ deadLetters: response.pollSyncEnvelopes.deadLetters ?? [],
7240
+ hasMore: response.pollSyncEnvelopes.hasMore
7100
7241
  };
7101
7242
  }
7102
7243
  /**
@@ -7452,7 +7593,8 @@ var GqlResponseChannel = class {
7452
7593
  lastSuccessUtcMs: 0,
7453
7594
  lastFailureUtcMs: 0,
7454
7595
  pushBlocked: false,
7455
- pushFailureCount: 0
7596
+ pushFailureCount: 0,
7597
+ receivingPages: false
7456
7598
  };
7457
7599
  }
7458
7600
  onConnectionStateChange(callback) {
@@ -8033,6 +8175,15 @@ function deriveStatus(inboxCount, outboxCount, errorCount) {
8033
8175
  }
8034
8176
  //#endregion
8035
8177
  //#region src/sync/sync-manager.ts
8178
+ var OutboxMode = /* @__PURE__ */ function(OutboxMode) {
8179
+ OutboxMode["Backfill"] = "backfill";
8180
+ OutboxMode["BatchTriggered"] = "batch-triggered";
8181
+ return OutboxMode;
8182
+ }(OutboxMode || {});
8183
+ const defaultSyncManagerConfig = {
8184
+ maxDeadLettersPerRemote: 100,
8185
+ maxInboxBatchSize: 32
8186
+ };
8036
8187
  var SyncManager = class {
8037
8188
  logger;
8038
8189
  remoteStorage;
@@ -8051,10 +8202,11 @@ var SyncManager = class {
8051
8202
  failedEventUnsubscribe;
8052
8203
  batchAggregator;
8053
8204
  syncStatusTracker;
8054
- maxDeadLettersPerRemote;
8205
+ config;
8055
8206
  connectionStateUnsubscribes = /* @__PURE__ */ new Map();
8056
8207
  quarantinedDocumentIds = /* @__PURE__ */ new Set();
8057
- constructor(logger, remoteStorage, cursorStorage, deadLetterStorage, channelFactory, operationIndex, reactor, eventBus, maxDeadLettersPerRemote = 100) {
8208
+ backfillAbortControllers = /* @__PURE__ */ new Map();
8209
+ constructor(logger, remoteStorage, cursorStorage, deadLetterStorage, channelFactory, operationIndex, reactor, eventBus, config = {}) {
8058
8210
  this.logger = logger;
8059
8211
  this.remoteStorage = remoteStorage;
8060
8212
  this.cursorStorage = cursorStorage;
@@ -8063,7 +8215,10 @@ var SyncManager = class {
8063
8215
  this.operationIndex = operationIndex;
8064
8216
  this.reactor = reactor;
8065
8217
  this.eventBus = eventBus;
8066
- this.maxDeadLettersPerRemote = maxDeadLettersPerRemote;
8218
+ this.config = {
8219
+ ...defaultSyncManagerConfig,
8220
+ ...config
8221
+ };
8067
8222
  this.remotes = /* @__PURE__ */ new Map();
8068
8223
  this.awaiter = new JobAwaiter(eventBus, (jobId, signal) => reactor.getJobStatus(jobId, signal));
8069
8224
  this.syncAwaiter = new SyncAwaiter(eventBus);
@@ -8101,7 +8256,16 @@ var SyncManager = class {
8101
8256
  continue;
8102
8257
  }
8103
8258
  const outboxAckOrdinal = remote.channel.outbox.ackOrdinal;
8104
- if (outboxAckOrdinal > 0) await this.updateOutbox(remote, outboxAckOrdinal);
8259
+ if (outboxAckOrdinal > 0) {
8260
+ const backfillController = new AbortController();
8261
+ this.backfillAbortControllers.set(record.name, backfillController);
8262
+ this.updateOutbox(remote, outboxAckOrdinal, OutboxMode.Backfill, backfillController.signal).catch((error) => {
8263
+ if (backfillController.signal.aborted) return;
8264
+ this.logger.error("Backfill failed for remote @RemoteName: @Error", remote.name, error instanceof Error ? error : new Error(String(error)));
8265
+ }).finally(() => {
8266
+ this.backfillAbortControllers.delete(record.name);
8267
+ });
8268
+ }
8105
8269
  }
8106
8270
  this.eventUnsubscribe = this.eventBus.subscribe(ReactorEventTypes.JOB_WRITE_READY, async (_type, event) => this.batchAggregator.enqueueWriteReady(event));
8107
8271
  this.failedEventUnsubscribe = this.eventBus.subscribe(ReactorEventTypes.JOB_FAILED, async (_type, event) => this.batchAggregator.handleJobFailed(event));
@@ -8109,6 +8273,8 @@ var SyncManager = class {
8109
8273
  shutdown() {
8110
8274
  this.isShutdown = true;
8111
8275
  this.abortController.abort();
8276
+ for (const controller of this.backfillAbortControllers.values()) controller.abort();
8277
+ this.backfillAbortControllers.clear();
8112
8278
  this.batchAggregator.clear();
8113
8279
  if (this.eventUnsubscribe) {
8114
8280
  this.eventUnsubscribe();
@@ -8181,12 +8347,24 @@ var SyncManager = class {
8181
8347
  await this.remoteStorage.remove(name);
8182
8348
  throw error;
8183
8349
  }
8184
- await this.updateOutbox(remote, 0);
8350
+ const backfillController = new AbortController();
8351
+ this.backfillAbortControllers.set(name, backfillController);
8352
+ this.updateOutbox(remote, 0, OutboxMode.Backfill, backfillController.signal).catch((error) => {
8353
+ if (backfillController.signal.aborted) return;
8354
+ this.logger.error("Backfill failed for remote @RemoteName: @Error", remote.name, error instanceof Error ? error : new Error(String(error)));
8355
+ }).finally(() => {
8356
+ this.backfillAbortControllers.delete(name);
8357
+ });
8185
8358
  return remote;
8186
8359
  }
8187
8360
  async remove(name) {
8188
8361
  const remote = this.remotes.get(name);
8189
8362
  if (!remote) throw new Error(`Remote with name '${name}' does not exist`);
8363
+ const backfillController = this.backfillAbortControllers.get(name);
8364
+ if (backfillController) {
8365
+ backfillController.abort();
8366
+ this.backfillAbortControllers.delete(name);
8367
+ }
8190
8368
  await remote.channel.shutdown();
8191
8369
  await this.remoteStorage.remove(name);
8192
8370
  await this.cursorStorage.remove(name);
@@ -8251,8 +8429,8 @@ var SyncManager = class {
8251
8429
  }).catch(() => {});
8252
8430
  }
8253
8431
  const items = remote.channel.deadLetter.items;
8254
- if (items.length > this.maxDeadLettersPerRemote) {
8255
- const excessCount = items.length - this.maxDeadLettersPerRemote;
8432
+ if (items.length > this.config.maxDeadLettersPerRemote) {
8433
+ const excessCount = items.length - this.config.maxDeadLettersPerRemote;
8256
8434
  const toEvict = items.slice(0, excessCount);
8257
8435
  remote.channel.deadLetter.remove(...toEvict);
8258
8436
  }
@@ -8263,7 +8441,7 @@ var SyncManager = class {
8263
8441
  try {
8264
8442
  records = (await this.deadLetterStorage.list(remote.name, {
8265
8443
  cursor: "0",
8266
- limit: this.maxDeadLettersPerRemote
8444
+ limit: this.config.maxDeadLettersPerRemote
8267
8445
  })).results;
8268
8446
  } catch (error) {
8269
8447
  this.logger.error("Failed to load dead letters for remote (@name, @error)", remote.name, error instanceof Error ? error.message : String(error));
@@ -8292,7 +8470,7 @@ var SyncManager = class {
8292
8470
  for (const remote of remotes) if (!affectedRemotes.includes(remote)) affectedRemotes.push(remote);
8293
8471
  }
8294
8472
  for (const remote of affectedRemotes) trimMailboxFromBatch(remote.channel.inbox, batch);
8295
- for (const remote of affectedRemotes) await this.updateOutbox(remote, remote.channel.outbox.latestOrdinal);
8473
+ for (const remote of affectedRemotes) await this.updateOutbox(remote, remote.channel.outbox.latestOrdinal, OutboxMode.BatchTriggered);
8296
8474
  }
8297
8475
  handleInboxAdded(remote, syncOps) {
8298
8476
  if (this.isShutdown) return;
@@ -8303,10 +8481,19 @@ var SyncManager = class {
8303
8481
  for (const syncOp of eligible) if (syncOp.jobId) keyed.push(syncOp);
8304
8482
  else nonKeyed.push(syncOp);
8305
8483
  for (const syncOp of nonKeyed) this.applyInboxJob(remote, syncOp);
8306
- if (keyed.length > 0) this.applyInboxBatch(keyed.map((syncOp) => ({
8307
- remote,
8308
- syncOp
8309
- })));
8484
+ if (keyed.length > 0) {
8485
+ const chunks = chunkSyncOperations(keyed.map((syncOp) => ({
8486
+ remote,
8487
+ syncOp
8488
+ })), this.config.maxInboxBatchSize);
8489
+ this.processInboxChunks(chunks);
8490
+ }
8491
+ }
8492
+ async processInboxChunks(chunks) {
8493
+ for (const chunk of chunks) {
8494
+ if (this.isShutdown) return;
8495
+ await this.applyInboxBatch(chunk);
8496
+ }
8310
8497
  }
8311
8498
  async applyInboxJob(remote, syncOp) {
8312
8499
  const operations = syncOp.operations.map((op) => op.operation);
@@ -8348,13 +8535,14 @@ var SyncManager = class {
8348
8535
  }
8349
8536
  async applyInboxBatch(items) {
8350
8537
  const sourceRemote = items[0].remote.name;
8538
+ const chunkKeys = new Set(items.map(({ syncOp }) => syncOp.jobId));
8351
8539
  const request = { jobs: items.map(({ syncOp }) => ({
8352
8540
  key: syncOp.jobId,
8353
8541
  documentId: syncOp.documentId,
8354
8542
  scope: syncOp.scopes[0],
8355
8543
  branch: syncOp.branch,
8356
8544
  operations: syncOp.operations.map((op) => op.operation),
8357
- dependsOn: syncOp.jobDependencies.filter(Boolean)
8545
+ dependsOn: syncOp.jobDependencies.filter((dep) => dep && chunkKeys.has(dep))
8358
8546
  })) };
8359
8547
  let result;
8360
8548
  try {
@@ -8401,13 +8589,15 @@ var SyncManager = class {
8401
8589
  remote.channel.inbox.remove(syncOp);
8402
8590
  }
8403
8591
  }
8404
- async updateOutbox(remote, ackOrdinal) {
8592
+ async updateOutbox(remote, ackOrdinal, mode = OutboxMode.Backfill, signal) {
8593
+ const composedSignal = signal ? AbortSignal.any([signal, this.abortController.signal]) : this.abortController.signal;
8405
8594
  let maxOrdinal = ackOrdinal;
8406
8595
  const lastJobByDoc = /* @__PURE__ */ new Map();
8407
8596
  const sinceTimestamp = remote.options.sinceTimestampUtcMs;
8408
- let page = await this.operationIndex.find(remote.collectionId, ackOrdinal, { excludeSourceRemote: remote.name }, void 0, this.abortController.signal);
8597
+ let page = await this.operationIndex.find(remote.collectionId, ackOrdinal, { excludeSourceRemote: remote.name }, void 0, composedSignal);
8409
8598
  let hasMore;
8410
8599
  do {
8600
+ if (composedSignal.aborted) return;
8411
8601
  for (const entry of page.results) maxOrdinal = Math.max(maxOrdinal, entry.ordinal ?? 0);
8412
8602
  let operations = page.results.map((entry) => toOperationWithContext(entry));
8413
8603
  if (sinceTimestamp && sinceTimestamp !== "0") operations = operations.filter((op) => op.operation.timestampUtcMs >= sinceTimestamp);
@@ -8421,12 +8611,17 @@ var SyncManager = class {
8421
8611
  });
8422
8612
  const batches = batchOperationsByDocument(operations);
8423
8613
  const syncOps = [];
8614
+ let prevChainJobId;
8424
8615
  for (const batch of batches) {
8425
8616
  const jobId = crypto.randomUUID();
8426
8617
  const prevJobId = lastJobByDoc.get(batch.documentId);
8427
- const syncOp = new SyncOperation(crypto.randomUUID(), jobId, prevJobId ? [prevJobId] : [], remote.name, batch.documentId, [batch.scope], batch.branch, batch.operations);
8618
+ const deps = [];
8619
+ if (prevJobId) deps.push(prevJobId);
8620
+ if (mode === OutboxMode.BatchTriggered && prevChainJobId && prevChainJobId !== prevJobId) deps.push(prevChainJobId);
8621
+ const syncOp = new SyncOperation(crypto.randomUUID(), jobId, deps, remote.name, batch.documentId, [batch.scope], batch.branch, batch.operations);
8428
8622
  syncOps.push(syncOp);
8429
8623
  lastJobByDoc.set(batch.documentId, jobId);
8624
+ if (mode === OutboxMode.BatchTriggered) prevChainJobId = jobId;
8430
8625
  }
8431
8626
  remote.channel.outbox.add(...syncOps);
8432
8627
  }
@@ -8443,7 +8638,7 @@ var SyncBuilder = class {
8443
8638
  remoteStorage;
8444
8639
  cursorStorage;
8445
8640
  deadLetterStorage;
8446
- maxDeadLettersPerRemote = 100;
8641
+ config = {};
8447
8642
  withChannelFactory(factory) {
8448
8643
  this.channelFactory = factory;
8449
8644
  return this;
@@ -8461,7 +8656,11 @@ var SyncBuilder = class {
8461
8656
  return this;
8462
8657
  }
8463
8658
  withMaxDeadLettersPerRemote(limit) {
8464
- this.maxDeadLettersPerRemote = limit;
8659
+ this.config.maxDeadLettersPerRemote = limit;
8660
+ return this;
8661
+ }
8662
+ withMaxInboxBatchSize(limit) {
8663
+ this.config.maxInboxBatchSize = limit;
8465
8664
  return this;
8466
8665
  }
8467
8666
  build(reactor, logger, operationIndex, eventBus, db) {
@@ -8472,7 +8671,7 @@ var SyncBuilder = class {
8472
8671
  const remoteStorage = this.remoteStorage ?? new KyselySyncRemoteStorage(db);
8473
8672
  const cursorStorage = this.cursorStorage ?? new KyselySyncCursorStorage(db);
8474
8673
  const deadLetterStorage = this.deadLetterStorage ?? new KyselySyncDeadLetterStorage(db);
8475
- const syncManager = new SyncManager(logger, remoteStorage, cursorStorage, deadLetterStorage, this.channelFactory, operationIndex, reactor, eventBus, this.maxDeadLettersPerRemote);
8674
+ const syncManager = new SyncManager(logger, remoteStorage, cursorStorage, deadLetterStorage, this.channelFactory, operationIndex, reactor, eventBus, this.config);
8476
8675
  return {
8477
8676
  remoteStorage,
8478
8677
  cursorStorage,
@@ -8514,12 +8713,6 @@ function createMutableShutdownStatus(initialState = false) {
8514
8713
  ];
8515
8714
  }
8516
8715
  //#endregion
8517
- //#region src/shared/utils.ts
8518
- function matchesScope(view = {}, scope) {
8519
- if (view.scopes) return view.scopes.includes(scope);
8520
- return true;
8521
- }
8522
- //#endregion
8523
8716
  //#region src/core/types.ts
8524
8717
  var AbortError = class extends Error {
8525
8718
  constructor(message) {