@powerhousedao/reactor 6.0.0-dev.69 → 6.0.0-dev.77
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/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +19998 -62
- package/dist/src/sync/index.d.ts +1 -1
- package/dist/src/sync/index.d.ts.map +1 -1
- package/dist/src/sync/sync-manager.d.ts.map +1 -1
- package/dist/src/sync/types.d.ts +11 -0
- package/dist/src/sync/types.d.ts.map +1 -1
- package/package.json +17 -10
- package/dist/src/actions/index.js +0 -76
- package/dist/src/actions/index.js.map +0 -1
- package/dist/src/cache/buffer/ring-buffer.js +0 -69
- package/dist/src/cache/buffer/ring-buffer.js.map +0 -1
- package/dist/src/cache/collection-membership-cache.js +0 -33
- package/dist/src/cache/collection-membership-cache.js.map +0 -1
- package/dist/src/cache/document-meta-cache-types.js +0 -2
- package/dist/src/cache/document-meta-cache-types.js.map +0 -1
- package/dist/src/cache/document-meta-cache.js +0 -129
- package/dist/src/cache/document-meta-cache.js.map +0 -1
- package/dist/src/cache/index.js +0 -2
- package/dist/src/cache/index.js.map +0 -1
- package/dist/src/cache/kysely-operation-index.js +0 -345
- package/dist/src/cache/kysely-operation-index.js.map +0 -1
- package/dist/src/cache/kysely-write-cache.js +0 -411
- package/dist/src/cache/kysely-write-cache.js.map +0 -1
- package/dist/src/cache/lru/lru-tracker.js +0 -96
- package/dist/src/cache/lru/lru-tracker.js.map +0 -1
- package/dist/src/cache/operation-index-types.js +0 -4
- package/dist/src/cache/operation-index-types.js.map +0 -1
- package/dist/src/cache/write/interfaces.js +0 -2
- package/dist/src/cache/write/interfaces.js.map +0 -1
- package/dist/src/cache/write-cache-types.js +0 -2
- package/dist/src/cache/write-cache-types.js.map +0 -1
- package/dist/src/client/reactor-client.js +0 -497
- package/dist/src/client/reactor-client.js.map +0 -1
- package/dist/src/client/types.js +0 -14
- package/dist/src/client/types.js.map +0 -1
- package/dist/src/core/reactor-builder.js +0 -306
- package/dist/src/core/reactor-builder.js.map +0 -1
- package/dist/src/core/reactor-client-builder.js +0 -132
- package/dist/src/core/reactor-client-builder.js.map +0 -1
- package/dist/src/core/reactor.js +0 -640
- package/dist/src/core/reactor.js.map +0 -1
- package/dist/src/core/types.js +0 -2
- package/dist/src/core/types.js.map +0 -1
- package/dist/src/core/utils.js +0 -225
- package/dist/src/core/utils.js.map +0 -1
- package/dist/src/events/event-bus.js +0 -53
- package/dist/src/events/event-bus.js.map +0 -1
- package/dist/src/events/interfaces.js +0 -2
- package/dist/src/events/interfaces.js.map +0 -1
- package/dist/src/events/types.js +0 -30
- package/dist/src/events/types.js.map +0 -1
- package/dist/src/executor/document-action-handler.js +0 -356
- package/dist/src/executor/document-action-handler.js.map +0 -1
- package/dist/src/executor/interfaces.js +0 -2
- package/dist/src/executor/interfaces.js.map +0 -1
- package/dist/src/executor/signature-verifier.js +0 -70
- package/dist/src/executor/signature-verifier.js.map +0 -1
- package/dist/src/executor/simple-job-executor-manager.js +0 -345
- package/dist/src/executor/simple-job-executor-manager.js.map +0 -1
- package/dist/src/executor/simple-job-executor.js +0 -423
- package/dist/src/executor/simple-job-executor.js.map +0 -1
- package/dist/src/executor/types.js +0 -11
- package/dist/src/executor/types.js.map +0 -1
- package/dist/src/executor/util.js +0 -230
- package/dist/src/executor/util.js.map +0 -1
- package/dist/src/index.js.map +0 -1
- package/dist/src/job-tracker/in-memory-job-tracker.js +0 -114
- package/dist/src/job-tracker/in-memory-job-tracker.js.map +0 -1
- package/dist/src/job-tracker/index.js +0 -2
- package/dist/src/job-tracker/index.js.map +0 -1
- package/dist/src/job-tracker/interfaces.js +0 -2
- package/dist/src/job-tracker/interfaces.js.map +0 -1
- package/dist/src/logging/console.js +0 -2
- package/dist/src/logging/console.js.map +0 -1
- package/dist/src/logging/types.js +0 -2
- package/dist/src/logging/types.js.map +0 -1
- package/dist/src/processors/index.js +0 -2
- package/dist/src/processors/index.js.map +0 -1
- package/dist/src/processors/processor-manager.js +0 -165
- package/dist/src/processors/processor-manager.js.map +0 -1
- package/dist/src/processors/relational/types.js +0 -2
- package/dist/src/processors/relational/types.js.map +0 -1
- package/dist/src/processors/relational/utils.js +0 -2
- package/dist/src/processors/relational/utils.js.map +0 -1
- package/dist/src/processors/utils.js +0 -59
- package/dist/src/processors/utils.js.map +0 -1
- package/dist/src/queue/interfaces.js +0 -2
- package/dist/src/queue/interfaces.js.map +0 -1
- package/dist/src/queue/job-execution-handle.js +0 -71
- package/dist/src/queue/job-execution-handle.js.map +0 -1
- package/dist/src/queue/queue.js +0 -493
- package/dist/src/queue/queue.js.map +0 -1
- package/dist/src/queue/types.js +0 -19
- package/dist/src/queue/types.js.map +0 -1
- package/dist/src/read-models/base-read-model.js +0 -143
- package/dist/src/read-models/base-read-model.js.map +0 -1
- package/dist/src/read-models/coordinator.js +0 -72
- package/dist/src/read-models/coordinator.js.map +0 -1
- package/dist/src/read-models/document-view.js +0 -457
- package/dist/src/read-models/document-view.js.map +0 -1
- package/dist/src/read-models/interfaces.js +0 -2
- package/dist/src/read-models/interfaces.js.map +0 -1
- package/dist/src/read-models/types.js +0 -2
- package/dist/src/read-models/types.js.map +0 -1
- package/dist/src/registry/document-model-resolver.js +0 -81
- package/dist/src/registry/document-model-resolver.js.map +0 -1
- package/dist/src/registry/implementation.js +0 -226
- package/dist/src/registry/implementation.js.map +0 -1
- package/dist/src/registry/index.js +0 -3
- package/dist/src/registry/index.js.map +0 -1
- package/dist/src/registry/interfaces.js +0 -2
- package/dist/src/registry/interfaces.js.map +0 -1
- package/dist/src/shared/awaiter.js +0 -123
- package/dist/src/shared/awaiter.js.map +0 -1
- package/dist/src/shared/collect-all-pages.js +0 -17
- package/dist/src/shared/collect-all-pages.js.map +0 -1
- package/dist/src/shared/consistency-tracker.js +0 -123
- package/dist/src/shared/consistency-tracker.js.map +0 -1
- package/dist/src/shared/drive-url.js +0 -17
- package/dist/src/shared/drive-url.js.map +0 -1
- package/dist/src/shared/errors.js +0 -93
- package/dist/src/shared/errors.js.map +0 -1
- package/dist/src/shared/factories.js +0 -41
- package/dist/src/shared/factories.js.map +0 -1
- package/dist/src/shared/types.js +0 -38
- package/dist/src/shared/types.js.map +0 -1
- package/dist/src/shared/utils.js +0 -8
- package/dist/src/shared/utils.js.map +0 -1
- package/dist/src/signer/passthrough-signer.js +0 -17
- package/dist/src/signer/passthrough-signer.js.map +0 -1
- package/dist/src/signer/types.js +0 -2
- package/dist/src/signer/types.js.map +0 -1
- package/dist/src/storage/index.js +0 -3
- package/dist/src/storage/index.js.map +0 -1
- package/dist/src/storage/interfaces.js +0 -29
- package/dist/src/storage/interfaces.js.map +0 -1
- package/dist/src/storage/kysely/document-indexer.js +0 -421
- package/dist/src/storage/kysely/document-indexer.js.map +0 -1
- package/dist/src/storage/kysely/keyframe-store.js +0 -64
- package/dist/src/storage/kysely/keyframe-store.js.map +0 -1
- package/dist/src/storage/kysely/store.js +0 -264
- package/dist/src/storage/kysely/store.js.map +0 -1
- package/dist/src/storage/kysely/sync-cursor-storage.js +0 -97
- package/dist/src/storage/kysely/sync-cursor-storage.js.map +0 -1
- package/dist/src/storage/kysely/sync-dead-letter-storage.js +0 -110
- package/dist/src/storage/kysely/sync-dead-letter-storage.js.map +0 -1
- package/dist/src/storage/kysely/sync-remote-storage.js +0 -133
- package/dist/src/storage/kysely/sync-remote-storage.js.map +0 -1
- package/dist/src/storage/kysely/types.js +0 -2
- package/dist/src/storage/kysely/types.js.map +0 -1
- package/dist/src/storage/migrations/001_create_operation_table.js +0 -41
- package/dist/src/storage/migrations/001_create_operation_table.js.map +0 -1
- package/dist/src/storage/migrations/002_create_keyframe_table.js +0 -27
- package/dist/src/storage/migrations/002_create_keyframe_table.js.map +0 -1
- package/dist/src/storage/migrations/003_create_document_table.js +0 -10
- package/dist/src/storage/migrations/003_create_document_table.js.map +0 -1
- package/dist/src/storage/migrations/004_create_document_relationship_table.js +0 -35
- package/dist/src/storage/migrations/004_create_document_relationship_table.js.map +0 -1
- package/dist/src/storage/migrations/005_create_indexer_state_table.js +0 -10
- package/dist/src/storage/migrations/005_create_indexer_state_table.js.map +0 -1
- package/dist/src/storage/migrations/006_create_document_snapshot_table.js +0 -49
- package/dist/src/storage/migrations/006_create_document_snapshot_table.js.map +0 -1
- package/dist/src/storage/migrations/007_create_slug_mapping_table.js +0 -24
- package/dist/src/storage/migrations/007_create_slug_mapping_table.js.map +0 -1
- package/dist/src/storage/migrations/008_create_view_state_table.js +0 -10
- package/dist/src/storage/migrations/008_create_view_state_table.js.map +0 -1
- package/dist/src/storage/migrations/009_create_operation_index_tables.js +0 -50
- package/dist/src/storage/migrations/009_create_operation_index_tables.js.map +0 -1
- package/dist/src/storage/migrations/010_create_sync_tables.js +0 -43
- package/dist/src/storage/migrations/010_create_sync_tables.js.map +0 -1
- package/dist/src/storage/migrations/011_add_cursor_type_column.js +0 -29
- package/dist/src/storage/migrations/011_add_cursor_type_column.js.map +0 -1
- package/dist/src/storage/migrations/012_add_source_remote_column.js +0 -7
- package/dist/src/storage/migrations/012_add_source_remote_column.js.map +0 -1
- package/dist/src/storage/migrations/013_create_sync_dead_letters_table.js +0 -24
- package/dist/src/storage/migrations/013_create_sync_dead_letters_table.js.map +0 -1
- package/dist/src/storage/migrations/index.js +0 -3
- package/dist/src/storage/migrations/index.js.map +0 -1
- package/dist/src/storage/migrations/migrator.js +0 -84
- package/dist/src/storage/migrations/migrator.js.map +0 -1
- package/dist/src/storage/migrations/run-migrations.js +0 -58
- package/dist/src/storage/migrations/run-migrations.js.map +0 -1
- package/dist/src/storage/migrations/types.js +0 -2
- package/dist/src/storage/migrations/types.js.map +0 -1
- package/dist/src/storage/txn.js +0 -42
- package/dist/src/storage/txn.js.map +0 -1
- package/dist/src/subs/default-error-handler.js +0 -27
- package/dist/src/subs/default-error-handler.js.map +0 -1
- package/dist/src/subs/react-subscription-manager.js +0 -185
- package/dist/src/subs/react-subscription-manager.js.map +0 -1
- package/dist/src/subs/subscription-notification-read-model.js +0 -62
- package/dist/src/subs/subscription-notification-read-model.js.map +0 -1
- package/dist/src/subs/types.js +0 -2
- package/dist/src/subs/types.js.map +0 -1
- package/dist/src/sync/batch-aggregator.js +0 -94
- package/dist/src/sync/batch-aggregator.js.map +0 -1
- package/dist/src/sync/buffered-mailbox.js +0 -164
- package/dist/src/sync/buffered-mailbox.js.map +0 -1
- package/dist/src/sync/channels/gql-req-channel.js +0 -548
- package/dist/src/sync/channels/gql-req-channel.js.map +0 -1
- package/dist/src/sync/channels/gql-request-channel-factory.js +0 -105
- package/dist/src/sync/channels/gql-request-channel-factory.js.map +0 -1
- package/dist/src/sync/channels/gql-res-channel.js +0 -79
- package/dist/src/sync/channels/gql-res-channel.js.map +0 -1
- package/dist/src/sync/channels/gql-response-channel-factory.js +0 -14
- package/dist/src/sync/channels/gql-response-channel-factory.js.map +0 -1
- package/dist/src/sync/channels/index.js +0 -8
- package/dist/src/sync/channels/index.js.map +0 -1
- package/dist/src/sync/channels/interval-poll-timer.js +0 -123
- package/dist/src/sync/channels/interval-poll-timer.js.map +0 -1
- package/dist/src/sync/channels/poll-timer.js +0 -2
- package/dist/src/sync/channels/poll-timer.js.map +0 -1
- package/dist/src/sync/channels/utils.js +0 -161
- package/dist/src/sync/channels/utils.js.map +0 -1
- package/dist/src/sync/errors.js +0 -17
- package/dist/src/sync/errors.js.map +0 -1
- package/dist/src/sync/index.js +0 -11
- package/dist/src/sync/index.js.map +0 -1
- package/dist/src/sync/interfaces.js +0 -2
- package/dist/src/sync/interfaces.js.map +0 -1
- package/dist/src/sync/mailbox.js +0 -142
- package/dist/src/sync/mailbox.js.map +0 -1
- package/dist/src/sync/sync-awaiter.js +0 -124
- package/dist/src/sync/sync-awaiter.js.map +0 -1
- package/dist/src/sync/sync-builder.js +0 -52
- package/dist/src/sync/sync-builder.js.map +0 -1
- package/dist/src/sync/sync-manager.js +0 -447
- package/dist/src/sync/sync-manager.js.map +0 -1
- package/dist/src/sync/sync-operation.js +0 -70
- package/dist/src/sync/sync-operation.js.map +0 -1
- package/dist/src/sync/sync-status-tracker.js +0 -137
- package/dist/src/sync/sync-status-tracker.js.map +0 -1
- package/dist/src/sync/types.js +0 -31
- package/dist/src/sync/types.js.map +0 -1
- package/dist/src/sync/utils.js +0 -283
- package/dist/src/sync/utils.js.map +0 -1
- package/dist/src/utils/reshuffle.js +0 -91
- package/dist/src/utils/reshuffle.js.map +0 -1
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import { SyncEventTypes, } from "./types.js";
|
|
2
|
-
/**
|
|
3
|
-
* Provides a promise-based interface for waiting on sync completion.
|
|
4
|
-
* Subscribes to sync events at construction and tracks completed sync results
|
|
5
|
-
* to provide a fast path for jobs that have already synced.
|
|
6
|
-
*/
|
|
7
|
-
export class SyncAwaiter {
|
|
8
|
-
eventBus;
|
|
9
|
-
completedResults = new Map();
|
|
10
|
-
pendingWaiters = new Map();
|
|
11
|
-
unsubscribers = [];
|
|
12
|
-
isShutdown = false;
|
|
13
|
-
constructor(eventBus) {
|
|
14
|
-
this.eventBus = eventBus;
|
|
15
|
-
this.subscribeToEvents();
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Waits for sync operations for a job to complete.
|
|
19
|
-
* Resolves when SYNC_SUCCEEDED is emitted.
|
|
20
|
-
* Rejects when SYNC_FAILED is emitted.
|
|
21
|
-
*
|
|
22
|
-
* @param jobId - The job id to wait for
|
|
23
|
-
* @param signal - Optional abort signal
|
|
24
|
-
* @returns The sync result
|
|
25
|
-
*/
|
|
26
|
-
waitForSync(jobId, signal) {
|
|
27
|
-
if (signal?.aborted) {
|
|
28
|
-
return Promise.reject(new Error("Operation aborted"));
|
|
29
|
-
}
|
|
30
|
-
if (this.isShutdown) {
|
|
31
|
-
return Promise.reject(new Error("SyncAwaiter is shutdown"));
|
|
32
|
-
}
|
|
33
|
-
const completedResult = this.completedResults.get(jobId);
|
|
34
|
-
if (completedResult) {
|
|
35
|
-
return Promise.resolve(completedResult);
|
|
36
|
-
}
|
|
37
|
-
return new Promise((resolve, reject) => {
|
|
38
|
-
const waiter = { resolve, reject, signal };
|
|
39
|
-
const existingWaiters = this.pendingWaiters.get(jobId) || [];
|
|
40
|
-
existingWaiters.push(waiter);
|
|
41
|
-
this.pendingWaiters.set(jobId, existingWaiters);
|
|
42
|
-
if (signal) {
|
|
43
|
-
const abortHandler = () => {
|
|
44
|
-
const waiters = this.pendingWaiters.get(jobId);
|
|
45
|
-
if (waiters) {
|
|
46
|
-
const index = waiters.indexOf(waiter);
|
|
47
|
-
if (index !== -1) {
|
|
48
|
-
waiters.splice(index, 1);
|
|
49
|
-
if (waiters.length === 0) {
|
|
50
|
-
this.pendingWaiters.delete(jobId);
|
|
51
|
-
}
|
|
52
|
-
waiter.reject(new Error("Operation aborted"));
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
|
-
signal.addEventListener("abort", abortHandler, { once: true });
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Shuts down the sync awaiter. This will synchronously reject all pending waiters.
|
|
62
|
-
*/
|
|
63
|
-
shutdown() {
|
|
64
|
-
this.isShutdown = true;
|
|
65
|
-
for (const unsubscribe of this.unsubscribers) {
|
|
66
|
-
unsubscribe();
|
|
67
|
-
}
|
|
68
|
-
this.unsubscribers.length = 0;
|
|
69
|
-
for (const [, waiters] of this.pendingWaiters) {
|
|
70
|
-
for (const waiter of waiters) {
|
|
71
|
-
waiter.reject(new Error("SyncAwaiter shutdown"));
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
this.pendingWaiters.clear();
|
|
75
|
-
}
|
|
76
|
-
subscribeToEvents() {
|
|
77
|
-
this.unsubscribers.push(this.eventBus.subscribe(SyncEventTypes.SYNC_SUCCEEDED, (_type, event) => {
|
|
78
|
-
this.handleSyncSucceeded(event);
|
|
79
|
-
}));
|
|
80
|
-
this.unsubscribers.push(this.eventBus.subscribe(SyncEventTypes.SYNC_FAILED, (_type, event) => {
|
|
81
|
-
this.handleSyncFailed(event);
|
|
82
|
-
}));
|
|
83
|
-
}
|
|
84
|
-
handleSyncSucceeded(event) {
|
|
85
|
-
const result = {
|
|
86
|
-
jobId: event.jobId,
|
|
87
|
-
status: "succeeded",
|
|
88
|
-
syncOperationCount: event.syncOperationCount,
|
|
89
|
-
successCount: event.syncOperationCount,
|
|
90
|
-
failureCount: 0,
|
|
91
|
-
errors: [],
|
|
92
|
-
};
|
|
93
|
-
this.completedResults.set(event.jobId, result);
|
|
94
|
-
this.resolveWaiters(event.jobId, result);
|
|
95
|
-
}
|
|
96
|
-
handleSyncFailed(event) {
|
|
97
|
-
const result = {
|
|
98
|
-
jobId: event.jobId,
|
|
99
|
-
status: "failed",
|
|
100
|
-
syncOperationCount: event.successCount + event.failureCount,
|
|
101
|
-
successCount: event.successCount,
|
|
102
|
-
failureCount: event.failureCount,
|
|
103
|
-
errors: event.errors,
|
|
104
|
-
};
|
|
105
|
-
this.completedResults.set(event.jobId, result);
|
|
106
|
-
this.resolveWaiters(event.jobId, result);
|
|
107
|
-
}
|
|
108
|
-
resolveWaiters(jobId, result) {
|
|
109
|
-
const waiters = this.pendingWaiters.get(jobId);
|
|
110
|
-
if (!waiters || waiters.length === 0) {
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
this.pendingWaiters.delete(jobId);
|
|
114
|
-
for (const waiter of waiters) {
|
|
115
|
-
if (waiter.signal?.aborted) {
|
|
116
|
-
waiter.reject(new Error("Operation aborted"));
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
waiter.resolve(result);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
//# sourceMappingURL=sync-awaiter.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sync-awaiter.js","sourceRoot":"","sources":["../../../src/sync/sync-awaiter.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { KyselySyncCursorStorage } from "../storage/kysely/sync-cursor-storage.js";
|
|
2
|
-
import { KyselySyncDeadLetterStorage } from "../storage/kysely/sync-dead-letter-storage.js";
|
|
3
|
-
import { KyselySyncRemoteStorage } from "../storage/kysely/sync-remote-storage.js";
|
|
4
|
-
import { SyncManager } from "./sync-manager.js";
|
|
5
|
-
export class SyncBuilder {
|
|
6
|
-
channelFactory;
|
|
7
|
-
remoteStorage;
|
|
8
|
-
cursorStorage;
|
|
9
|
-
deadLetterStorage;
|
|
10
|
-
maxDeadLettersPerRemote = 100;
|
|
11
|
-
withChannelFactory(factory) {
|
|
12
|
-
this.channelFactory = factory;
|
|
13
|
-
return this;
|
|
14
|
-
}
|
|
15
|
-
withRemoteStorage(storage) {
|
|
16
|
-
this.remoteStorage = storage;
|
|
17
|
-
return this;
|
|
18
|
-
}
|
|
19
|
-
withCursorStorage(storage) {
|
|
20
|
-
this.cursorStorage = storage;
|
|
21
|
-
return this;
|
|
22
|
-
}
|
|
23
|
-
withDeadLetterStorage(storage) {
|
|
24
|
-
this.deadLetterStorage = storage;
|
|
25
|
-
return this;
|
|
26
|
-
}
|
|
27
|
-
withMaxDeadLettersPerRemote(limit) {
|
|
28
|
-
this.maxDeadLettersPerRemote = limit;
|
|
29
|
-
return this;
|
|
30
|
-
}
|
|
31
|
-
build(reactor, logger, operationIndex, eventBus, db) {
|
|
32
|
-
const module = this.buildModule(reactor, logger, operationIndex, eventBus, db);
|
|
33
|
-
return module.syncManager;
|
|
34
|
-
}
|
|
35
|
-
buildModule(reactor, logger, operationIndex, eventBus, db) {
|
|
36
|
-
if (!this.channelFactory) {
|
|
37
|
-
throw new Error("Channel factory is required");
|
|
38
|
-
}
|
|
39
|
-
const remoteStorage = this.remoteStorage ?? new KyselySyncRemoteStorage(db);
|
|
40
|
-
const cursorStorage = this.cursorStorage ?? new KyselySyncCursorStorage(db);
|
|
41
|
-
const deadLetterStorage = this.deadLetterStorage ?? new KyselySyncDeadLetterStorage(db);
|
|
42
|
-
const syncManager = new SyncManager(logger, remoteStorage, cursorStorage, deadLetterStorage, this.channelFactory, operationIndex, reactor, eventBus, this.maxDeadLettersPerRemote);
|
|
43
|
-
return {
|
|
44
|
-
remoteStorage,
|
|
45
|
-
cursorStorage,
|
|
46
|
-
deadLetterStorage,
|
|
47
|
-
channelFactory: this.channelFactory,
|
|
48
|
-
syncManager,
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
//# sourceMappingURL=sync-builder.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sync-builder.js","sourceRoot":"","sources":["../../../src/sync/sync-builder.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,uBAAuB,EAAE,MAAM,0CAA0C,CAAC;AACnF,OAAO,EAAE,2BAA2B,EAAE,MAAM,+CAA+C,CAAC;AAC5F,OAAO,EAAE,uBAAuB,EAAE,MAAM,0CAA0C,CAAC;AAGnF,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,OAAO,WAAW;IACd,cAAc,CAAmB;IACjC,aAAa,CAAsB;IACnC,aAAa,CAAsB;IACnC,iBAAiB,CAA0B;IAC3C,uBAAuB,GAAW,GAAG,CAAC;IAE9C,kBAAkB,CAAC,OAAwB;QACzC,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC;QAC9B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,iBAAiB,CAAC,OAA2B;QAC3C,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC;QAC7B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,iBAAiB,CAAC,OAA2B;QAC3C,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC;QAC7B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,qBAAqB,CAAC,OAA+B;QACnD,IAAI,CAAC,iBAAiB,GAAG,OAAO,CAAC;QACjC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,2BAA2B,CAAC,KAAa;QACvC,IAAI,CAAC,uBAAuB,GAAG,KAAK,CAAC;QACrC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,CACH,OAAiB,EACjB,MAAe,EACf,cAA+B,EAC/B,QAAmB,EACnB,EAAoB;QAEpB,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAC7B,OAAO,EACP,MAAM,EACN,cAAc,EACd,QAAQ,EACR,EAAE,CACH,CAAC;QACF,OAAO,MAAM,CAAC,WAAW,CAAC;IAC5B,CAAC;IAED,WAAW,CACT,OAAiB,EACjB,MAAe,EACf,cAA+B,EAC/B,QAAmB,EACnB,EAAoB;QAEpB,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;QACjD,CAAC;QAED,MAAM,aAAa,GAAG,IAAI,CAAC,aAAa,IAAI,IAAI,uBAAuB,CAAC,EAAE,CAAC,CAAC;QAC5E,MAAM,aAAa,GAAG,IAAI,CAAC,aAAa,IAAI,IAAI,uBAAuB,CAAC,EAAE,CAAC,CAAC;QAC5E,MAAM,iBAAiB,GACrB,IAAI,CAAC,iBAAiB,IAAI,IAAI,2BAA2B,CAAC,EAAE,CAAC,CAAC;QAEhE,MAAM,WAAW,GAAG,IAAI,WAAW,CACjC,MAAM,EACN,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,IAAI,CAAC,cAAc,EACnB,cAAc,EACd,OAAO,EACP,QAAQ,EACR,IAAI,CAAC,uBAAuB,CAC7B,CAAC;QAEF,OAAO;YACL,aAAa;YACb,aAAa;YACb,iBAAiB;YACjB,cAAc,EAAE,IAAI,CAAC,cAAc;YACnC,WAAW;SACZ,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -1,447 +0,0 @@
|
|
|
1
|
-
import { ReactorEventTypes, } from "../events/types.js";
|
|
2
|
-
import { JobAwaiter } from "../shared/awaiter.js";
|
|
3
|
-
import { JobStatus, } from "../shared/types.js";
|
|
4
|
-
import { BatchAggregator } from "./batch-aggregator.js";
|
|
5
|
-
import { ChannelError } from "./errors.js";
|
|
6
|
-
import { SyncAwaiter } from "./sync-awaiter.js";
|
|
7
|
-
import { SyncOperation } from "./sync-operation.js";
|
|
8
|
-
import { SyncStatusTracker, } from "./sync-status-tracker.js";
|
|
9
|
-
import { ChannelErrorSource } from "./types.js";
|
|
10
|
-
import { batchOperationsByDocument, createIdleHealth, filterOperations, toOperationWithContext, trimMailboxFromBatch, } from "./utils.js";
|
|
11
|
-
export class SyncManager {
|
|
12
|
-
logger;
|
|
13
|
-
remoteStorage;
|
|
14
|
-
cursorStorage;
|
|
15
|
-
deadLetterStorage;
|
|
16
|
-
channelFactory;
|
|
17
|
-
operationIndex;
|
|
18
|
-
reactor;
|
|
19
|
-
eventBus;
|
|
20
|
-
remotes;
|
|
21
|
-
awaiter;
|
|
22
|
-
syncAwaiter;
|
|
23
|
-
isShutdown;
|
|
24
|
-
eventUnsubscribe;
|
|
25
|
-
failedEventUnsubscribe;
|
|
26
|
-
batchAggregator;
|
|
27
|
-
syncStatusTracker;
|
|
28
|
-
maxDeadLettersPerRemote;
|
|
29
|
-
loadJobs = new Map();
|
|
30
|
-
constructor(logger, remoteStorage, cursorStorage, deadLetterStorage, channelFactory, operationIndex, reactor, eventBus, maxDeadLettersPerRemote = 100) {
|
|
31
|
-
this.logger = logger;
|
|
32
|
-
this.remoteStorage = remoteStorage;
|
|
33
|
-
this.cursorStorage = cursorStorage;
|
|
34
|
-
this.deadLetterStorage = deadLetterStorage;
|
|
35
|
-
this.channelFactory = channelFactory;
|
|
36
|
-
this.operationIndex = operationIndex;
|
|
37
|
-
this.reactor = reactor;
|
|
38
|
-
this.eventBus = eventBus;
|
|
39
|
-
this.maxDeadLettersPerRemote = maxDeadLettersPerRemote;
|
|
40
|
-
this.remotes = new Map();
|
|
41
|
-
this.awaiter = new JobAwaiter(eventBus, (jobId, signal) => reactor.getJobStatus(jobId, signal));
|
|
42
|
-
this.syncAwaiter = new SyncAwaiter(eventBus);
|
|
43
|
-
this.isShutdown = false;
|
|
44
|
-
this.batchAggregator = new BatchAggregator(logger, (batch) => this.processCompleteBatch(batch));
|
|
45
|
-
this.syncStatusTracker = new SyncStatusTracker();
|
|
46
|
-
}
|
|
47
|
-
async startup() {
|
|
48
|
-
if (this.isShutdown) {
|
|
49
|
-
throw new Error("SyncManager is already shutdown and cannot be started");
|
|
50
|
-
}
|
|
51
|
-
const remoteRecords = await this.remoteStorage.list();
|
|
52
|
-
for (const record of remoteRecords) {
|
|
53
|
-
const channel = this.channelFactory.instance(record.id, record.name, record.channelConfig, this.cursorStorage, record.collectionId, record.filter, this.operationIndex);
|
|
54
|
-
const remote = {
|
|
55
|
-
id: record.id,
|
|
56
|
-
name: record.name,
|
|
57
|
-
collectionId: record.collectionId,
|
|
58
|
-
filter: record.filter,
|
|
59
|
-
options: record.options,
|
|
60
|
-
channel,
|
|
61
|
-
};
|
|
62
|
-
this.remotes.set(record.name, remote);
|
|
63
|
-
await this.loadDeadLetters(remote);
|
|
64
|
-
this.wireChannelCallbacks(remote);
|
|
65
|
-
try {
|
|
66
|
-
await channel.init();
|
|
67
|
-
}
|
|
68
|
-
catch (error) {
|
|
69
|
-
this.logger.error("Error initializing channel for remote (@name, @error)", record.name, error instanceof Error ? error.message : String(error));
|
|
70
|
-
this.remotes.delete(record.name);
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
// backfill channels
|
|
74
|
-
const outboxAckOrdinal = remote.channel.outbox.ackOrdinal;
|
|
75
|
-
if (outboxAckOrdinal > 0) {
|
|
76
|
-
await this.updateOutbox(remote, outboxAckOrdinal);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
this.eventUnsubscribe = this.eventBus.subscribe(ReactorEventTypes.JOB_WRITE_READY, async (_type, event) => this.batchAggregator.enqueueWriteReady(event));
|
|
80
|
-
this.failedEventUnsubscribe = this.eventBus.subscribe(ReactorEventTypes.JOB_FAILED, async (_type, event) => this.batchAggregator.handleJobFailed(event));
|
|
81
|
-
}
|
|
82
|
-
shutdown() {
|
|
83
|
-
this.isShutdown = true;
|
|
84
|
-
this.batchAggregator.clear();
|
|
85
|
-
if (this.eventUnsubscribe) {
|
|
86
|
-
this.eventUnsubscribe();
|
|
87
|
-
this.eventUnsubscribe = undefined;
|
|
88
|
-
}
|
|
89
|
-
if (this.failedEventUnsubscribe) {
|
|
90
|
-
this.failedEventUnsubscribe();
|
|
91
|
-
this.failedEventUnsubscribe = undefined;
|
|
92
|
-
}
|
|
93
|
-
this.awaiter.shutdown();
|
|
94
|
-
this.syncAwaiter.shutdown();
|
|
95
|
-
this.syncStatusTracker.clear();
|
|
96
|
-
const promises = [];
|
|
97
|
-
for (const remote of this.remotes.values()) {
|
|
98
|
-
promises.push(remote.channel.shutdown());
|
|
99
|
-
}
|
|
100
|
-
this.remotes.clear();
|
|
101
|
-
return {
|
|
102
|
-
isShutdown: true,
|
|
103
|
-
completed: Promise.all(promises).then(() => undefined),
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
getByName(name) {
|
|
107
|
-
const remote = this.remotes.get(name);
|
|
108
|
-
if (!remote) {
|
|
109
|
-
throw new Error(`Remote with name '${name}' does not exist`);
|
|
110
|
-
}
|
|
111
|
-
return remote;
|
|
112
|
-
}
|
|
113
|
-
getById(id) {
|
|
114
|
-
for (const remote of this.remotes.values()) {
|
|
115
|
-
if (remote.id === id) {
|
|
116
|
-
return remote;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
throw new Error(`Remote with id '${id}' does not exist`);
|
|
120
|
-
}
|
|
121
|
-
async add(name, collectionId, channelConfig, filter = { documentId: [], scope: [], branch: "" }, options = { sinceTimestampUtcMs: "0" }, id) {
|
|
122
|
-
if (this.isShutdown) {
|
|
123
|
-
throw new Error("SyncManager is shutdown and cannot add remotes");
|
|
124
|
-
}
|
|
125
|
-
if (this.remotes.has(name)) {
|
|
126
|
-
throw new Error(`Remote with name '${name}' already exists`);
|
|
127
|
-
}
|
|
128
|
-
this.logger.debug("Adding remote (@name, @collectionId, @channelConfig, @filter, @options, @id)", name, collectionId, channelConfig, filter, options, id);
|
|
129
|
-
const remoteId = id ?? crypto.randomUUID();
|
|
130
|
-
const status = {
|
|
131
|
-
push: createIdleHealth(),
|
|
132
|
-
pull: createIdleHealth(),
|
|
133
|
-
};
|
|
134
|
-
const remoteRecord = {
|
|
135
|
-
id: remoteId,
|
|
136
|
-
name,
|
|
137
|
-
collectionId,
|
|
138
|
-
channelConfig,
|
|
139
|
-
filter,
|
|
140
|
-
options,
|
|
141
|
-
status,
|
|
142
|
-
};
|
|
143
|
-
await this.remoteStorage.upsert(remoteRecord);
|
|
144
|
-
const channel = this.channelFactory.instance(remoteId, name, channelConfig, this.cursorStorage, collectionId, filter, this.operationIndex);
|
|
145
|
-
const remote = {
|
|
146
|
-
id: remoteId,
|
|
147
|
-
name,
|
|
148
|
-
collectionId,
|
|
149
|
-
filter,
|
|
150
|
-
options,
|
|
151
|
-
channel,
|
|
152
|
-
};
|
|
153
|
-
this.remotes.set(name, remote);
|
|
154
|
-
await this.loadDeadLetters(remote);
|
|
155
|
-
this.wireChannelCallbacks(remote);
|
|
156
|
-
try {
|
|
157
|
-
await channel.init();
|
|
158
|
-
}
|
|
159
|
-
catch (error) {
|
|
160
|
-
this.remotes.delete(name);
|
|
161
|
-
await this.remoteStorage.remove(name);
|
|
162
|
-
throw error;
|
|
163
|
-
}
|
|
164
|
-
// backfill
|
|
165
|
-
await this.updateOutbox(remote, 0);
|
|
166
|
-
return remote;
|
|
167
|
-
}
|
|
168
|
-
async remove(name) {
|
|
169
|
-
const remote = this.remotes.get(name);
|
|
170
|
-
if (!remote) {
|
|
171
|
-
throw new Error(`Remote with name '${name}' does not exist`);
|
|
172
|
-
}
|
|
173
|
-
// shutdown the channel
|
|
174
|
-
await remote.channel.shutdown();
|
|
175
|
-
// delete the remote's data
|
|
176
|
-
await this.remoteStorage.remove(name);
|
|
177
|
-
await this.cursorStorage.remove(name);
|
|
178
|
-
this.syncStatusTracker.untrackRemote(name);
|
|
179
|
-
this.remotes.delete(name);
|
|
180
|
-
}
|
|
181
|
-
list() {
|
|
182
|
-
return Array.from(this.remotes.values());
|
|
183
|
-
}
|
|
184
|
-
waitForSync(jobId, signal) {
|
|
185
|
-
return this.syncAwaiter.waitForSync(jobId, signal);
|
|
186
|
-
}
|
|
187
|
-
getSyncStatus(documentId) {
|
|
188
|
-
return this.syncStatusTracker.getStatus(documentId);
|
|
189
|
-
}
|
|
190
|
-
onSyncStatusChange(callback) {
|
|
191
|
-
return this.syncStatusTracker.onChange(callback);
|
|
192
|
-
}
|
|
193
|
-
wireChannelCallbacks(remote) {
|
|
194
|
-
remote.channel.inbox.onAdded((syncOps) => this.handleInboxAdded(remote, syncOps));
|
|
195
|
-
this.syncStatusTracker.trackRemote(remote.name, remote.channel);
|
|
196
|
-
remote.channel.deadLetter.onAdded((syncOps) => {
|
|
197
|
-
for (const syncOp of syncOps) {
|
|
198
|
-
this.logger.error("Dead letter (@remote, @documentId, @jobId, @error, @dependencies)", remote.name, syncOp.documentId, syncOp.jobId, syncOp.error?.message ?? "unknown", syncOp.jobDependencies);
|
|
199
|
-
const record = {
|
|
200
|
-
id: syncOp.id,
|
|
201
|
-
jobId: syncOp.jobId,
|
|
202
|
-
jobDependencies: syncOp.jobDependencies,
|
|
203
|
-
remoteName: syncOp.remoteName,
|
|
204
|
-
documentId: syncOp.documentId,
|
|
205
|
-
scopes: syncOp.scopes,
|
|
206
|
-
branch: syncOp.branch,
|
|
207
|
-
operations: syncOp.operations,
|
|
208
|
-
errorSource: syncOp.error?.source ?? ChannelErrorSource.None,
|
|
209
|
-
errorMessage: syncOp.error?.error.message ?? "unknown",
|
|
210
|
-
};
|
|
211
|
-
void this.deadLetterStorage.add(record).catch((err) => {
|
|
212
|
-
this.logger.error("Failed to persist dead letter (@id, @error)", record.id, err instanceof Error ? err.message : String(err));
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
// Evict oldest dead letters from mailbox if over capacity
|
|
216
|
-
const items = remote.channel.deadLetter.items;
|
|
217
|
-
if (items.length > this.maxDeadLettersPerRemote) {
|
|
218
|
-
const excessCount = items.length - this.maxDeadLettersPerRemote;
|
|
219
|
-
const toEvict = items.slice(0, excessCount);
|
|
220
|
-
remote.channel.deadLetter.remove(...toEvict);
|
|
221
|
-
}
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
async loadDeadLetters(remote) {
|
|
225
|
-
let records;
|
|
226
|
-
try {
|
|
227
|
-
const page = await this.deadLetterStorage.list(remote.name, {
|
|
228
|
-
cursor: "0",
|
|
229
|
-
limit: this.maxDeadLettersPerRemote,
|
|
230
|
-
});
|
|
231
|
-
records = page.results;
|
|
232
|
-
}
|
|
233
|
-
catch (error) {
|
|
234
|
-
this.logger.error("Failed to load dead letters for remote (@name, @error)", remote.name, error instanceof Error ? error.message : String(error));
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
if (records.length === 0) {
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
// Records come in ordinal DESC order (newest first).
|
|
241
|
-
// Reverse so the Map maintains chronological insertion order (oldest first),
|
|
242
|
-
// which makes eviction (slice from the front) straightforward.
|
|
243
|
-
records.reverse();
|
|
244
|
-
const syncOps = [];
|
|
245
|
-
for (const record of records) {
|
|
246
|
-
const syncOp = new SyncOperation(record.id, record.jobId, record.jobDependencies, record.remoteName, record.documentId, record.scopes, record.branch, record.operations);
|
|
247
|
-
syncOp.failed(new ChannelError(record.errorSource, new Error(record.errorMessage)));
|
|
248
|
-
syncOps.push(syncOp);
|
|
249
|
-
}
|
|
250
|
-
remote.channel.deadLetter.add(...syncOps);
|
|
251
|
-
this.logger.debug("Loaded @count persisted dead letters for remote @name", records.length, remote.name);
|
|
252
|
-
}
|
|
253
|
-
getRemotesForCollection(collectionId) {
|
|
254
|
-
return Array.from(this.remotes.values()).filter((remote) => remote.collectionId === collectionId);
|
|
255
|
-
}
|
|
256
|
-
async processCompleteBatch(batch) {
|
|
257
|
-
// get the unique set of collection ids
|
|
258
|
-
const collectionIds = [
|
|
259
|
-
...new Set(Object.values(batch.collectionMemberships).flatMap((collections) => collections)),
|
|
260
|
-
];
|
|
261
|
-
// get the unique set of affected remotes
|
|
262
|
-
const affectedRemotes = [];
|
|
263
|
-
for (const collectionId of collectionIds) {
|
|
264
|
-
const remotes = this.getRemotesForCollection(collectionId);
|
|
265
|
-
for (const remote of remotes) {
|
|
266
|
-
if (!affectedRemotes.includes(remote)) {
|
|
267
|
-
affectedRemotes.push(remote);
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
// ack matching inbox items
|
|
272
|
-
for (const remote of affectedRemotes) {
|
|
273
|
-
trimMailboxFromBatch(remote.channel.inbox, batch);
|
|
274
|
-
}
|
|
275
|
-
// finally, work through the affected remotes and backfill based on the last operation in the outbox
|
|
276
|
-
for (const remote of affectedRemotes) {
|
|
277
|
-
await this.updateOutbox(remote, remote.channel.outbox.latestOrdinal);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
handleInboxAdded(remote, syncOps) {
|
|
281
|
-
if (this.isShutdown) {
|
|
282
|
-
return;
|
|
283
|
-
}
|
|
284
|
-
const keyed = [];
|
|
285
|
-
const nonKeyed = [];
|
|
286
|
-
for (const syncOp of syncOps) {
|
|
287
|
-
if (syncOp.jobId) {
|
|
288
|
-
keyed.push(syncOp);
|
|
289
|
-
}
|
|
290
|
-
else {
|
|
291
|
-
nonKeyed.push(syncOp);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
for (const syncOp of nonKeyed) {
|
|
295
|
-
void this.applyInboxJob(remote, syncOp);
|
|
296
|
-
}
|
|
297
|
-
if (keyed.length > 0) {
|
|
298
|
-
void this.applyInboxBatch(keyed.map((syncOp) => ({ remote, syncOp })));
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
async applyInboxJob(remote, syncOp) {
|
|
302
|
-
const operations = syncOp.operations.map((op) => op.operation);
|
|
303
|
-
let jobInfo;
|
|
304
|
-
try {
|
|
305
|
-
jobInfo = await this.reactor.load(syncOp.documentId, syncOp.branch, operations, undefined, { sourceRemote: remote.name });
|
|
306
|
-
}
|
|
307
|
-
catch (error) {
|
|
308
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
309
|
-
this.logger.error("Failed to load operations from inbox (@remote, @documentId, @error)", remote.name, syncOp.documentId, err.message);
|
|
310
|
-
const channelError = new ChannelError(ChannelErrorSource.Inbox, err);
|
|
311
|
-
syncOp.failed(channelError);
|
|
312
|
-
remote.channel.deadLetter.add(syncOp);
|
|
313
|
-
remote.channel.inbox.remove(syncOp);
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
let completedJobInfo;
|
|
317
|
-
try {
|
|
318
|
-
completedJobInfo = await this.awaiter.waitForJob(jobInfo.id);
|
|
319
|
-
}
|
|
320
|
-
catch (error) {
|
|
321
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
322
|
-
this.logger.error("Failed to wait for job completion (@remote, @documentId, @jobId, @error)", remote.name, syncOp.documentId, jobInfo.id, err.message);
|
|
323
|
-
const channelError = new ChannelError(ChannelErrorSource.Inbox, err);
|
|
324
|
-
syncOp.failed(channelError);
|
|
325
|
-
remote.channel.deadLetter.add(syncOp);
|
|
326
|
-
remote.channel.inbox.remove(syncOp);
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
const jobKey = `${syncOp.documentId}:${syncOp.branch}`;
|
|
330
|
-
this.loadJobs.set(jobKey, completedJobInfo);
|
|
331
|
-
if (completedJobInfo.status === JobStatus.FAILED) {
|
|
332
|
-
const errorMessage = completedJobInfo.error?.message || "Unknown error";
|
|
333
|
-
this.logger.error("Failed to apply operations from inbox (@remote, @documentId, @jobId, @error)", remote.name, syncOp.documentId, completedJobInfo.id, errorMessage);
|
|
334
|
-
const error = new ChannelError(ChannelErrorSource.Inbox, new Error(`Failed to apply operations: ${errorMessage}`));
|
|
335
|
-
syncOp.failed(error);
|
|
336
|
-
remote.channel.deadLetter.add(syncOp);
|
|
337
|
-
}
|
|
338
|
-
else {
|
|
339
|
-
syncOp.executed();
|
|
340
|
-
}
|
|
341
|
-
remote.channel.inbox.remove(syncOp);
|
|
342
|
-
}
|
|
343
|
-
async applyInboxBatch(items) {
|
|
344
|
-
const sourceRemote = items[0].remote.name;
|
|
345
|
-
const jobs = items.map(({ syncOp }) => ({
|
|
346
|
-
key: syncOp.jobId,
|
|
347
|
-
documentId: syncOp.documentId,
|
|
348
|
-
scope: syncOp.scopes[0],
|
|
349
|
-
branch: syncOp.branch,
|
|
350
|
-
operations: syncOp.operations.map((op) => op.operation),
|
|
351
|
-
dependsOn: syncOp.jobDependencies.filter(Boolean),
|
|
352
|
-
}));
|
|
353
|
-
const request = { jobs };
|
|
354
|
-
let result;
|
|
355
|
-
try {
|
|
356
|
-
result = await this.reactor.loadBatch(request, undefined, {
|
|
357
|
-
sourceRemote,
|
|
358
|
-
});
|
|
359
|
-
}
|
|
360
|
-
catch (error) {
|
|
361
|
-
for (const { remote, syncOp } of items) {
|
|
362
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
363
|
-
syncOp.failed(new ChannelError(ChannelErrorSource.Inbox, err));
|
|
364
|
-
remote.channel.deadLetter.add(syncOp);
|
|
365
|
-
remote.channel.inbox.remove(syncOp);
|
|
366
|
-
}
|
|
367
|
-
return;
|
|
368
|
-
}
|
|
369
|
-
for (const { remote, syncOp } of items) {
|
|
370
|
-
if (!(syncOp.jobId in result.jobs)) {
|
|
371
|
-
this.logger.error("Job key missing from batch load result (@remote, @documentId, @jobId)", remote.name, syncOp.documentId, syncOp.jobId);
|
|
372
|
-
const error = new ChannelError(ChannelErrorSource.Inbox, new Error(`Job key '${syncOp.jobId}' missing from batch load result`));
|
|
373
|
-
syncOp.failed(error);
|
|
374
|
-
remote.channel.deadLetter.add(syncOp);
|
|
375
|
-
remote.channel.inbox.remove(syncOp);
|
|
376
|
-
continue;
|
|
377
|
-
}
|
|
378
|
-
const jobInfo = result.jobs[syncOp.jobId];
|
|
379
|
-
let completedJobInfo;
|
|
380
|
-
try {
|
|
381
|
-
completedJobInfo = await this.awaiter.waitForJob(jobInfo.id);
|
|
382
|
-
}
|
|
383
|
-
catch (error) {
|
|
384
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
385
|
-
syncOp.failed(new ChannelError(ChannelErrorSource.Inbox, err));
|
|
386
|
-
remote.channel.deadLetter.add(syncOp);
|
|
387
|
-
remote.channel.inbox.remove(syncOp);
|
|
388
|
-
continue;
|
|
389
|
-
}
|
|
390
|
-
const jobKey = `${syncOp.documentId}:${syncOp.branch}`;
|
|
391
|
-
this.loadJobs.set(jobKey, completedJobInfo);
|
|
392
|
-
if (completedJobInfo.status === JobStatus.FAILED) {
|
|
393
|
-
const errorMessage = completedJobInfo.error?.message || "Unknown error";
|
|
394
|
-
const channelError = new ChannelError(ChannelErrorSource.Inbox, new Error(`Failed to apply operations: ${errorMessage}`));
|
|
395
|
-
syncOp.failed(channelError);
|
|
396
|
-
remote.channel.deadLetter.add(syncOp);
|
|
397
|
-
}
|
|
398
|
-
else {
|
|
399
|
-
syncOp.executed();
|
|
400
|
-
}
|
|
401
|
-
remote.channel.inbox.remove(syncOp);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
async updateOutbox(remote, ackOrdinal) {
|
|
405
|
-
const operations = await this.getOperationsForRemote(remote, ackOrdinal);
|
|
406
|
-
if (operations.length === 0) {
|
|
407
|
-
return;
|
|
408
|
-
}
|
|
409
|
-
// sort by (documentId, scope, ordinal) so batchOperationsByDocument
|
|
410
|
-
// groups all operations for the same document together
|
|
411
|
-
operations.sort((a, b) => {
|
|
412
|
-
if (a.context.documentId !== b.context.documentId) {
|
|
413
|
-
return a.context.documentId < b.context.documentId ? -1 : 1;
|
|
414
|
-
}
|
|
415
|
-
if (a.context.scope !== b.context.scope) {
|
|
416
|
-
return a.context.scope < b.context.scope ? -1 : 1;
|
|
417
|
-
}
|
|
418
|
-
return a.context.ordinal - b.context.ordinal;
|
|
419
|
-
});
|
|
420
|
-
const batches = batchOperationsByDocument(operations);
|
|
421
|
-
// per-document dependency chain: each batch depends on the previous
|
|
422
|
-
// batch for the same documentId only, allowing independent documents
|
|
423
|
-
// to be processed in parallel
|
|
424
|
-
const lastJobByDoc = new Map();
|
|
425
|
-
const syncOps = [];
|
|
426
|
-
for (const batch of batches) {
|
|
427
|
-
const jobId = crypto.randomUUID();
|
|
428
|
-
const prevJobId = lastJobByDoc.get(batch.documentId);
|
|
429
|
-
const syncOp = new SyncOperation(crypto.randomUUID(), jobId, prevJobId ? [prevJobId] : [], remote.name, batch.documentId, [batch.scope], batch.branch, batch.operations);
|
|
430
|
-
syncOps.push(syncOp);
|
|
431
|
-
lastJobByDoc.set(batch.documentId, jobId);
|
|
432
|
-
}
|
|
433
|
-
remote.channel.outbox.add(...syncOps);
|
|
434
|
-
}
|
|
435
|
-
async getOperationsForRemote(remote, ackOrdinal) {
|
|
436
|
-
const results = await this.operationIndex.find(remote.collectionId, ackOrdinal, { excludeSourceRemote: remote.name });
|
|
437
|
-
let operations = results.results.map((entry) => toOperationWithContext(entry));
|
|
438
|
-
// apply the sinceTimestampUtcMs filter
|
|
439
|
-
const sinceTimestamp = remote.options.sinceTimestampUtcMs;
|
|
440
|
-
if (sinceTimestamp && sinceTimestamp !== "0") {
|
|
441
|
-
operations = operations.filter((op) => op.operation.timestampUtcMs >= sinceTimestamp);
|
|
442
|
-
}
|
|
443
|
-
// apply the remote filter
|
|
444
|
-
return filterOperations(operations, remote.filter);
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
//# sourceMappingURL=sync-manager.js.map
|