@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.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +228 -35
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
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
|
-
|
|
574
|
-
|
|
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
|
-
|
|
8205
|
+
config;
|
|
8055
8206
|
connectionStateUnsubscribes = /* @__PURE__ */ new Map();
|
|
8056
8207
|
quarantinedDocumentIds = /* @__PURE__ */ new Set();
|
|
8057
|
-
|
|
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.
|
|
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)
|
|
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
|
-
|
|
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)
|
|
8307
|
-
|
|
8308
|
-
|
|
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(
|
|
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,
|
|
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
|
|
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
|
-
|
|
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.
|
|
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) {
|