@powerhousedao/reactor 6.0.0-dev.6 → 6.0.0-dev.60
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/cache/collection-membership-cache.d.ts +13 -0
- package/dist/src/cache/collection-membership-cache.d.ts.map +1 -0
- package/dist/src/cache/collection-membership-cache.js +33 -0
- package/dist/src/cache/collection-membership-cache.js.map +1 -0
- package/dist/src/cache/document-meta-cache.d.ts.map +1 -1
- package/dist/src/cache/document-meta-cache.js +6 -5
- package/dist/src/cache/document-meta-cache.js.map +1 -1
- package/dist/src/cache/kysely-operation-index.d.ts +6 -1
- package/dist/src/cache/kysely-operation-index.d.ts.map +1 -1
- package/dist/src/cache/kysely-operation-index.js +102 -7
- package/dist/src/cache/kysely-operation-index.js.map +1 -1
- package/dist/src/cache/kysely-write-cache.d.ts +9 -2
- package/dist/src/cache/kysely-write-cache.d.ts.map +1 -1
- package/dist/src/cache/kysely-write-cache.js +38 -15
- package/dist/src/cache/kysely-write-cache.js.map +1 -1
- package/dist/src/cache/operation-index-types.d.ts +16 -2
- package/dist/src/cache/operation-index-types.d.ts.map +1 -1
- package/dist/src/cache/operation-index-types.js.map +1 -1
- package/dist/src/cache/write/interfaces.d.ts +7 -2
- package/dist/src/cache/write/interfaces.d.ts.map +1 -1
- package/dist/src/client/reactor-client.d.ts +13 -10
- package/dist/src/client/reactor-client.d.ts.map +1 -1
- package/dist/src/client/reactor-client.js +134 -43
- package/dist/src/client/reactor-client.js.map +1 -1
- package/dist/src/client/types.d.ts +25 -6
- package/dist/src/client/types.d.ts.map +1 -1
- package/dist/src/client/types.js.map +1 -1
- package/dist/src/core/reactor-builder.d.ts +23 -7
- package/dist/src/core/reactor-builder.d.ts.map +1 -1
- package/dist/src/core/reactor-builder.js +99 -24
- package/dist/src/core/reactor-builder.js.map +1 -1
- package/dist/src/core/reactor-client-builder.d.ts +5 -4
- package/dist/src/core/reactor-client-builder.d.ts.map +1 -1
- package/dist/src/core/reactor-client-builder.js +14 -5
- package/dist/src/core/reactor-client-builder.js.map +1 -1
- package/dist/src/core/reactor.d.ts +20 -80
- package/dist/src/core/reactor.d.ts.map +1 -1
- package/dist/src/core/reactor.js +235 -576
- package/dist/src/core/reactor.js.map +1 -1
- package/dist/src/core/types.d.ts +64 -28
- package/dist/src/core/types.d.ts.map +1 -1
- package/dist/src/core/utils.d.ts +39 -3
- package/dist/src/core/utils.d.ts.map +1 -1
- package/dist/src/core/utils.js +63 -9
- package/dist/src/core/utils.js.map +1 -1
- package/dist/src/events/types.d.ts +35 -10
- package/dist/src/events/types.d.ts.map +1 -1
- package/dist/src/events/types.js +7 -5
- package/dist/src/events/types.js.map +1 -1
- package/dist/src/executor/document-action-handler.d.ts +37 -0
- package/dist/src/executor/document-action-handler.d.ts.map +1 -0
- package/dist/src/executor/document-action-handler.js +356 -0
- package/dist/src/executor/document-action-handler.js.map +1 -0
- package/dist/src/executor/signature-verifier.d.ts +9 -0
- package/dist/src/executor/signature-verifier.d.ts.map +1 -0
- package/dist/src/executor/signature-verifier.js +70 -0
- package/dist/src/executor/signature-verifier.js.map +1 -0
- package/dist/src/executor/simple-job-executor-manager.d.ts +6 -1
- package/dist/src/executor/simple-job-executor-manager.d.ts.map +1 -1
- package/dist/src/executor/simple-job-executor-manager.js +125 -13
- package/dist/src/executor/simple-job-executor-manager.js.map +1 -1
- package/dist/src/executor/simple-job-executor.d.ts +6 -46
- package/dist/src/executor/simple-job-executor.d.ts.map +1 -1
- package/dist/src/executor/simple-job-executor.js +113 -588
- package/dist/src/executor/simple-job-executor.js.map +1 -1
- package/dist/src/executor/types.d.ts +1 -3
- package/dist/src/executor/types.d.ts.map +1 -1
- package/dist/src/executor/types.js.map +1 -1
- package/dist/src/executor/util.d.ts +12 -2
- package/dist/src/executor/util.d.ts.map +1 -1
- package/dist/src/executor/util.js +47 -1
- package/dist/src/executor/util.js.map +1 -1
- package/dist/src/index.d.ts +11 -9
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +7 -6
- package/dist/src/index.js.map +1 -1
- package/dist/src/job-tracker/in-memory-job-tracker.d.ts +4 -3
- package/dist/src/job-tracker/in-memory-job-tracker.d.ts.map +1 -1
- package/dist/src/job-tracker/in-memory-job-tracker.js +20 -18
- package/dist/src/job-tracker/in-memory-job-tracker.js.map +1 -1
- package/dist/src/job-tracker/interfaces.d.ts +3 -1
- package/dist/src/job-tracker/interfaces.d.ts.map +1 -1
- package/dist/src/logging/console.d.ts +1 -22
- package/dist/src/logging/console.d.ts.map +1 -1
- package/dist/src/logging/console.js +1 -107
- package/dist/src/logging/console.js.map +1 -1
- package/dist/src/logging/types.d.ts +1 -11
- package/dist/src/logging/types.d.ts.map +1 -1
- package/dist/src/processors/index.d.ts +1 -1
- package/dist/src/processors/index.d.ts.map +1 -1
- package/dist/src/processors/index.js.map +1 -1
- package/dist/src/processors/processor-manager.d.ts +2 -2
- package/dist/src/processors/processor-manager.d.ts.map +1 -1
- package/dist/src/processors/processor-manager.js.map +1 -1
- package/dist/src/processors/relational/types.d.ts +2 -0
- package/dist/src/processors/relational/types.d.ts.map +1 -0
- package/dist/src/processors/relational/types.js +2 -0
- package/dist/src/processors/relational/types.js.map +1 -0
- package/dist/src/processors/relational/utils.d.ts +2 -0
- package/dist/src/processors/relational/utils.d.ts.map +1 -0
- package/dist/src/processors/relational/utils.js +2 -0
- package/dist/src/processors/relational/utils.js.map +1 -0
- package/dist/src/processors/utils.d.ts +2 -2
- package/dist/src/processors/utils.d.ts.map +1 -1
- package/dist/src/processors/utils.js +2 -1
- package/dist/src/processors/utils.js.map +1 -1
- package/dist/src/queue/job-execution-handle.d.ts +3 -0
- package/dist/src/queue/job-execution-handle.d.ts.map +1 -1
- package/dist/src/queue/job-execution-handle.js +9 -0
- package/dist/src/queue/job-execution-handle.js.map +1 -1
- package/dist/src/queue/queue.d.ts +30 -1
- package/dist/src/queue/queue.d.ts.map +1 -1
- package/dist/src/queue/queue.js +110 -1
- package/dist/src/queue/queue.js.map +1 -1
- package/dist/src/queue/types.d.ts +4 -3
- package/dist/src/queue/types.d.ts.map +1 -1
- package/dist/src/queue/types.js.map +1 -1
- package/dist/src/read-models/base-read-model.d.ts +1 -1
- package/dist/src/read-models/base-read-model.d.ts.map +1 -1
- package/dist/src/read-models/base-read-model.js +4 -4
- package/dist/src/read-models/base-read-model.js.map +1 -1
- package/dist/src/read-models/coordinator.d.ts +2 -2
- package/dist/src/read-models/coordinator.d.ts.map +1 -1
- package/dist/src/read-models/coordinator.js +8 -8
- package/dist/src/read-models/coordinator.js.map +1 -1
- package/dist/src/read-models/document-view.d.ts +6 -3
- package/dist/src/read-models/document-view.d.ts.map +1 -1
- package/dist/src/read-models/document-view.js +130 -48
- package/dist/src/read-models/document-view.js.map +1 -1
- package/dist/src/read-models/interfaces.d.ts +1 -1
- package/dist/src/read-models/interfaces.d.ts.map +1 -1
- package/dist/src/registry/document-model-resolver.d.ts +29 -0
- package/dist/src/registry/document-model-resolver.d.ts.map +1 -0
- package/dist/src/registry/document-model-resolver.js +81 -0
- package/dist/src/registry/document-model-resolver.js.map +1 -0
- package/dist/src/registry/implementation.d.ts +4 -0
- package/dist/src/registry/implementation.d.ts.map +1 -1
- package/dist/src/registry/implementation.js +10 -0
- package/dist/src/registry/implementation.js.map +1 -1
- package/dist/src/registry/index.d.ts +3 -1
- package/dist/src/registry/index.d.ts.map +1 -1
- package/dist/src/registry/index.js +1 -0
- package/dist/src/registry/index.js.map +1 -1
- package/dist/src/registry/interfaces.d.ts +8 -0
- package/dist/src/registry/interfaces.d.ts.map +1 -1
- package/dist/src/shared/awaiter.d.ts +2 -2
- package/dist/src/shared/awaiter.d.ts.map +1 -1
- package/dist/src/shared/awaiter.js +11 -11
- package/dist/src/shared/awaiter.js.map +1 -1
- package/dist/src/shared/collect-all-pages.d.ts +7 -0
- package/dist/src/shared/collect-all-pages.d.ts.map +1 -0
- package/dist/src/shared/collect-all-pages.js +17 -0
- package/dist/src/shared/collect-all-pages.js.map +1 -0
- package/dist/src/shared/drive-url.d.ts +15 -0
- package/dist/src/shared/drive-url.d.ts.map +1 -0
- package/dist/src/shared/drive-url.js +17 -0
- package/dist/src/shared/drive-url.js.map +1 -0
- package/dist/src/shared/errors.d.ts +9 -0
- package/dist/src/shared/errors.d.ts.map +1 -1
- package/dist/src/shared/errors.js +18 -0
- package/dist/src/shared/errors.js.map +1 -1
- package/dist/src/shared/factories.d.ts +6 -2
- package/dist/src/shared/factories.d.ts.map +1 -1
- package/dist/src/shared/factories.js +10 -2
- package/dist/src/shared/factories.js.map +1 -1
- package/dist/src/shared/types.d.ts +32 -6
- package/dist/src/shared/types.d.ts.map +1 -1
- package/dist/src/shared/types.js +4 -4
- package/dist/src/shared/types.js.map +1 -1
- package/dist/src/signer/passthrough-signer.d.ts +1 -1
- package/dist/src/signer/passthrough-signer.d.ts.map +1 -1
- package/dist/src/signer/passthrough-signer.js +1 -3
- package/dist/src/signer/passthrough-signer.js.map +1 -1
- package/dist/src/storage/interfaces.d.ts +238 -124
- package/dist/src/storage/interfaces.d.ts.map +1 -1
- package/dist/src/storage/interfaces.js +10 -0
- package/dist/src/storage/interfaces.js.map +1 -1
- package/dist/src/storage/kysely/document-indexer.d.ts +8 -7
- package/dist/src/storage/kysely/document-indexer.d.ts.map +1 -1
- package/dist/src/storage/kysely/document-indexer.js +123 -52
- package/dist/src/storage/kysely/document-indexer.js.map +1 -1
- package/dist/src/storage/kysely/store.d.ts +5 -4
- package/dist/src/storage/kysely/store.d.ts.map +1 -1
- package/dist/src/storage/kysely/store.js +52 -21
- package/dist/src/storage/kysely/store.js.map +1 -1
- package/dist/src/storage/kysely/sync-cursor-storage.d.ts +1 -1
- package/dist/src/storage/kysely/sync-cursor-storage.d.ts.map +1 -1
- package/dist/src/storage/kysely/sync-cursor-storage.js +6 -2
- package/dist/src/storage/kysely/sync-cursor-storage.js.map +1 -1
- package/dist/src/storage/kysely/sync-dead-letter-storage.d.ts +17 -0
- package/dist/src/storage/kysely/sync-dead-letter-storage.d.ts.map +1 -0
- package/dist/src/storage/kysely/sync-dead-letter-storage.js +110 -0
- package/dist/src/storage/kysely/sync-dead-letter-storage.js.map +1 -0
- package/dist/src/storage/kysely/sync-remote-storage.js +1 -1
- package/dist/src/storage/kysely/sync-remote-storage.js.map +1 -1
- package/dist/src/storage/kysely/types.d.ts +22 -0
- package/dist/src/storage/kysely/types.d.ts.map +1 -1
- package/dist/src/storage/migrations/011_add_cursor_type_column.d.ts +3 -0
- package/dist/src/storage/migrations/011_add_cursor_type_column.d.ts.map +1 -0
- package/dist/src/storage/migrations/011_add_cursor_type_column.js +29 -0
- package/dist/src/storage/migrations/011_add_cursor_type_column.js.map +1 -0
- package/dist/src/storage/migrations/012_add_source_remote_column.d.ts +3 -0
- package/dist/src/storage/migrations/012_add_source_remote_column.d.ts.map +1 -0
- package/dist/src/storage/migrations/012_add_source_remote_column.js +7 -0
- package/dist/src/storage/migrations/012_add_source_remote_column.js.map +1 -0
- package/dist/src/storage/migrations/013_create_sync_dead_letters_table.d.ts +3 -0
- package/dist/src/storage/migrations/013_create_sync_dead_letters_table.d.ts.map +1 -0
- package/dist/src/storage/migrations/013_create_sync_dead_letters_table.js +24 -0
- package/dist/src/storage/migrations/013_create_sync_dead_letters_table.js.map +1 -0
- package/dist/src/storage/migrations/migrator.d.ts.map +1 -1
- package/dist/src/storage/migrations/migrator.js +6 -0
- package/dist/src/storage/migrations/migrator.js.map +1 -1
- package/dist/src/subs/default-error-handler.d.ts.map +1 -1
- package/dist/src/subs/default-error-handler.js.map +1 -1
- package/dist/src/subs/subscription-notification-read-model.d.ts +3 -2
- package/dist/src/subs/subscription-notification-read-model.d.ts.map +1 -1
- package/dist/src/subs/subscription-notification-read-model.js +1 -1
- package/dist/src/subs/subscription-notification-read-model.js.map +1 -1
- package/dist/src/sync/batch-aggregator.d.ts +25 -0
- package/dist/src/sync/batch-aggregator.d.ts.map +1 -0
- package/dist/src/sync/batch-aggregator.js +94 -0
- package/dist/src/sync/batch-aggregator.js.map +1 -0
- package/dist/src/sync/buffered-mailbox.d.ts +36 -0
- package/dist/src/sync/buffered-mailbox.d.ts.map +1 -0
- package/dist/src/sync/buffered-mailbox.js +164 -0
- package/dist/src/sync/buffered-mailbox.js.map +1 -0
- package/dist/src/sync/channels/{gql-channel.d.ts → gql-req-channel.d.ts} +49 -42
- package/dist/src/sync/channels/gql-req-channel.d.ts.map +1 -0
- package/dist/src/sync/channels/gql-req-channel.js +548 -0
- package/dist/src/sync/channels/gql-req-channel.js.map +1 -0
- package/dist/src/sync/channels/gql-request-channel-factory.d.ts +32 -0
- package/dist/src/sync/channels/gql-request-channel-factory.d.ts.map +1 -0
- package/dist/src/sync/channels/gql-request-channel-factory.js +105 -0
- package/dist/src/sync/channels/gql-request-channel-factory.js.map +1 -0
- package/dist/src/sync/channels/gql-res-channel.d.ts +25 -0
- package/dist/src/sync/channels/gql-res-channel.d.ts.map +1 -0
- package/dist/src/sync/channels/gql-res-channel.js +79 -0
- package/dist/src/sync/channels/gql-res-channel.js.map +1 -0
- package/dist/src/sync/channels/gql-response-channel-factory.d.ts +13 -0
- package/dist/src/sync/channels/gql-response-channel-factory.d.ts.map +1 -0
- package/dist/src/sync/channels/gql-response-channel-factory.js +14 -0
- package/dist/src/sync/channels/gql-response-channel-factory.js.map +1 -0
- package/dist/src/sync/channels/index.d.ts +6 -4
- package/dist/src/sync/channels/index.d.ts.map +1 -1
- package/dist/src/sync/channels/index.js +6 -4
- package/dist/src/sync/channels/index.js.map +1 -1
- package/dist/src/sync/channels/interval-poll-timer.d.ts +40 -0
- package/dist/src/sync/channels/interval-poll-timer.d.ts.map +1 -0
- package/dist/src/sync/channels/interval-poll-timer.js +123 -0
- package/dist/src/sync/channels/interval-poll-timer.js.map +1 -0
- package/dist/src/sync/channels/poll-timer.d.ts +14 -0
- package/dist/src/sync/channels/poll-timer.d.ts.map +1 -0
- package/dist/src/sync/channels/poll-timer.js +2 -0
- package/dist/src/sync/channels/poll-timer.js.map +1 -0
- package/dist/src/sync/channels/utils.d.ts +15 -1
- package/dist/src/sync/channels/utils.d.ts.map +1 -1
- package/dist/src/sync/channels/utils.js +67 -2
- package/dist/src/sync/channels/utils.js.map +1 -1
- package/dist/src/sync/index.d.ts +10 -6
- package/dist/src/sync/index.d.ts.map +1 -1
- package/dist/src/sync/index.js +7 -5
- package/dist/src/sync/index.js.map +1 -1
- package/dist/src/sync/interfaces.d.ts +34 -21
- package/dist/src/sync/interfaces.d.ts.map +1 -1
- package/dist/src/sync/mailbox.d.ts +51 -12
- package/dist/src/sync/mailbox.d.ts.map +1 -1
- package/dist/src/sync/mailbox.js +89 -6
- package/dist/src/sync/mailbox.js.map +1 -1
- package/dist/src/sync/sync-awaiter.d.ts +34 -0
- package/dist/src/sync/sync-awaiter.d.ts.map +1 -0
- package/dist/src/sync/sync-awaiter.js +124 -0
- package/dist/src/sync/sync-awaiter.js.map +1 -0
- package/dist/src/sync/sync-builder.d.ts +5 -1
- package/dist/src/sync/sync-builder.d.ts.map +1 -1
- package/dist/src/sync/sync-builder.js +14 -1
- package/dist/src/sync/sync-builder.js.map +1 -1
- package/dist/src/sync/sync-manager.d.ts +21 -8
- package/dist/src/sync/sync-manager.d.ts.map +1 -1
- package/dist/src/sync/sync-manager.js +274 -93
- package/dist/src/sync/sync-manager.js.map +1 -1
- package/dist/src/sync/sync-operation.d.ts +4 -2
- package/dist/src/sync/sync-operation.d.ts.map +1 -1
- package/dist/src/sync/sync-operation.js +8 -1
- package/dist/src/sync/sync-operation.js.map +1 -1
- package/dist/src/sync/sync-status-tracker.d.ts +31 -0
- package/dist/src/sync/sync-status-tracker.d.ts.map +1 -0
- package/dist/src/sync/sync-status-tracker.js +137 -0
- package/dist/src/sync/sync-status-tracker.js.map +1 -0
- package/dist/src/sync/types.d.ts +79 -2
- package/dist/src/sync/types.d.ts.map +1 -1
- package/dist/src/sync/types.js +15 -0
- package/dist/src/sync/types.js.map +1 -1
- package/dist/src/sync/utils.d.ts +37 -2
- package/dist/src/sync/utils.d.ts.map +1 -1
- package/dist/src/sync/utils.js +205 -0
- package/dist/src/sync/utils.js.map +1 -1
- package/dist/src/utils/reshuffle.d.ts +22 -5
- package/dist/src/utils/reshuffle.d.ts.map +1 -1
- package/dist/src/utils/reshuffle.js +50 -6
- package/dist/src/utils/reshuffle.js.map +1 -1
- package/package.json +12 -15
- package/dist/src/processors/types.d.ts +0 -63
- package/dist/src/processors/types.d.ts.map +0 -1
- package/dist/src/processors/types.js +0 -2
- package/dist/src/processors/types.js.map +0 -1
- package/dist/src/storage/consistency-aware-legacy-storage.d.ts +0 -33
- package/dist/src/storage/consistency-aware-legacy-storage.d.ts.map +0 -1
- package/dist/src/storage/consistency-aware-legacy-storage.js +0 -65
- package/dist/src/storage/consistency-aware-legacy-storage.js.map +0 -1
- package/dist/src/sync/channels/composite-channel-factory.d.ts +0 -30
- package/dist/src/sync/channels/composite-channel-factory.d.ts.map +0 -1
- package/dist/src/sync/channels/composite-channel-factory.js +0 -87
- package/dist/src/sync/channels/composite-channel-factory.js.map +0 -1
- package/dist/src/sync/channels/gql-channel-factory.d.ts +0 -25
- package/dist/src/sync/channels/gql-channel-factory.d.ts.map +0 -1
- package/dist/src/sync/channels/gql-channel-factory.js +0 -76
- package/dist/src/sync/channels/gql-channel-factory.js.map +0 -1
- package/dist/src/sync/channels/gql-channel.d.ts.map +0 -1
- package/dist/src/sync/channels/gql-channel.js +0 -423
- package/dist/src/sync/channels/gql-channel.js.map +0 -1
- package/dist/src/sync/channels/polling-channel.d.ts +0 -39
- package/dist/src/sync/channels/polling-channel.d.ts.map +0 -1
- package/dist/src/sync/channels/polling-channel.js +0 -72
- package/dist/src/sync/channels/polling-channel.js.map +0 -1
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { KyselySyncCursorStorage } from "../storage/kysely/sync-cursor-storage.js";
|
|
2
|
+
import { KyselySyncDeadLetterStorage } from "../storage/kysely/sync-dead-letter-storage.js";
|
|
2
3
|
import { KyselySyncRemoteStorage } from "../storage/kysely/sync-remote-storage.js";
|
|
3
4
|
import { SyncManager } from "./sync-manager.js";
|
|
4
5
|
export class SyncBuilder {
|
|
5
6
|
channelFactory;
|
|
6
7
|
remoteStorage;
|
|
7
8
|
cursorStorage;
|
|
9
|
+
deadLetterStorage;
|
|
10
|
+
maxDeadLettersPerRemote = 100;
|
|
8
11
|
withChannelFactory(factory) {
|
|
9
12
|
this.channelFactory = factory;
|
|
10
13
|
return this;
|
|
@@ -17,6 +20,14 @@ export class SyncBuilder {
|
|
|
17
20
|
this.cursorStorage = storage;
|
|
18
21
|
return this;
|
|
19
22
|
}
|
|
23
|
+
withDeadLetterStorage(storage) {
|
|
24
|
+
this.deadLetterStorage = storage;
|
|
25
|
+
return this;
|
|
26
|
+
}
|
|
27
|
+
withMaxDeadLettersPerRemote(limit) {
|
|
28
|
+
this.maxDeadLettersPerRemote = limit;
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
20
31
|
build(reactor, logger, operationIndex, eventBus, db) {
|
|
21
32
|
const module = this.buildModule(reactor, logger, operationIndex, eventBus, db);
|
|
22
33
|
return module.syncManager;
|
|
@@ -27,10 +38,12 @@ export class SyncBuilder {
|
|
|
27
38
|
}
|
|
28
39
|
const remoteStorage = this.remoteStorage ?? new KyselySyncRemoteStorage(db);
|
|
29
40
|
const cursorStorage = this.cursorStorage ?? new KyselySyncCursorStorage(db);
|
|
30
|
-
const
|
|
41
|
+
const deadLetterStorage = this.deadLetterStorage ?? new KyselySyncDeadLetterStorage(db);
|
|
42
|
+
const syncManager = new SyncManager(logger, remoteStorage, cursorStorage, deadLetterStorage, this.channelFactory, operationIndex, reactor, eventBus, this.maxDeadLettersPerRemote);
|
|
31
43
|
return {
|
|
32
44
|
remoteStorage,
|
|
33
45
|
cursorStorage,
|
|
46
|
+
deadLetterStorage,
|
|
34
47
|
channelFactory: this.channelFactory,
|
|
35
48
|
syncManager,
|
|
36
49
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync-builder.js","sourceRoot":"","sources":["../../../src/sync/sync-builder.ts"],"names":[],"mappings":"
|
|
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"}
|
|
@@ -3,35 +3,48 @@ import type { IReactor } from "../core/types.js";
|
|
|
3
3
|
import type { IEventBus } from "../events/interfaces.js";
|
|
4
4
|
import type { ILogger } from "../logging/types.js";
|
|
5
5
|
import { type JobInfo, type ShutdownStatus } from "../shared/types.js";
|
|
6
|
-
import type { ISyncCursorStorage, ISyncRemoteStorage } from "../storage/interfaces.js";
|
|
6
|
+
import type { ISyncCursorStorage, ISyncDeadLetterStorage, ISyncRemoteStorage } from "../storage/interfaces.js";
|
|
7
7
|
import type { IChannelFactory, ISyncManager, Remote } from "./interfaces.js";
|
|
8
|
-
import
|
|
8
|
+
import { type SyncStatus, type SyncStatusChangeCallback } from "./sync-status-tracker.js";
|
|
9
|
+
import type { ChannelConfig, RemoteFilter, RemoteOptions, SyncResult } from "./types.js";
|
|
9
10
|
export declare class SyncManager implements ISyncManager {
|
|
10
11
|
private readonly logger;
|
|
11
12
|
private readonly remoteStorage;
|
|
12
13
|
private readonly cursorStorage;
|
|
14
|
+
private readonly deadLetterStorage;
|
|
13
15
|
private readonly channelFactory;
|
|
14
|
-
private readonly
|
|
16
|
+
private readonly operationIndex;
|
|
15
17
|
private readonly reactor;
|
|
16
18
|
private readonly eventBus;
|
|
17
19
|
private readonly remotes;
|
|
18
20
|
private readonly awaiter;
|
|
21
|
+
private readonly syncAwaiter;
|
|
19
22
|
private isShutdown;
|
|
20
23
|
private eventUnsubscribe?;
|
|
24
|
+
private failedEventUnsubscribe?;
|
|
25
|
+
private readonly batchAggregator;
|
|
26
|
+
private readonly syncStatusTracker;
|
|
27
|
+
private readonly maxDeadLettersPerRemote;
|
|
21
28
|
loadJobs: Map<string, JobInfo>;
|
|
22
|
-
constructor(logger: ILogger, remoteStorage: ISyncRemoteStorage, cursorStorage: ISyncCursorStorage, channelFactory: IChannelFactory, operationIndex: IOperationIndex, reactor: IReactor, eventBus: IEventBus);
|
|
29
|
+
constructor(logger: ILogger, remoteStorage: ISyncRemoteStorage, cursorStorage: ISyncCursorStorage, deadLetterStorage: ISyncDeadLetterStorage, channelFactory: IChannelFactory, operationIndex: IOperationIndex, reactor: IReactor, eventBus: IEventBus, maxDeadLettersPerRemote?: number);
|
|
23
30
|
startup(): Promise<void>;
|
|
24
31
|
shutdown(): ShutdownStatus;
|
|
25
32
|
getByName(name: string): Remote;
|
|
26
33
|
getById(id: string): Remote;
|
|
27
34
|
add(name: string, collectionId: string, channelConfig: ChannelConfig, filter?: RemoteFilter, options?: RemoteOptions, id?: string): Promise<Remote>;
|
|
28
|
-
private backfillOutbox;
|
|
29
35
|
remove(name: string): Promise<void>;
|
|
30
36
|
list(): Remote[];
|
|
37
|
+
waitForSync(jobId: string, signal?: AbortSignal): Promise<SyncResult>;
|
|
38
|
+
getSyncStatus(documentId: string): SyncStatus | undefined;
|
|
39
|
+
onSyncStatusChange(callback: SyncStatusChangeCallback): () => void;
|
|
31
40
|
private wireChannelCallbacks;
|
|
32
|
-
private
|
|
33
|
-
private
|
|
34
|
-
private
|
|
41
|
+
private loadDeadLetters;
|
|
42
|
+
private getRemotesForCollection;
|
|
43
|
+
private processCompleteBatch;
|
|
44
|
+
private handleInboxAdded;
|
|
35
45
|
private applyInboxJob;
|
|
46
|
+
private applyInboxBatch;
|
|
47
|
+
private updateOutbox;
|
|
48
|
+
private getOperationsForRemote;
|
|
36
49
|
}
|
|
37
50
|
//# sourceMappingURL=sync-manager.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync-manager.d.ts","sourceRoot":"","sources":["../../../src/sync/sync-manager.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sync-manager.d.ts","sourceRoot":"","sources":["../../../src/sync/sync-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,34 +1,48 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ReactorEventTypes, } from "../events/types.js";
|
|
2
2
|
import { JobAwaiter } from "../shared/awaiter.js";
|
|
3
3
|
import { JobStatus, } from "../shared/types.js";
|
|
4
|
+
import { BatchAggregator } from "./batch-aggregator.js";
|
|
4
5
|
import { ChannelError } from "./errors.js";
|
|
6
|
+
import { SyncAwaiter } from "./sync-awaiter.js";
|
|
5
7
|
import { SyncOperation } from "./sync-operation.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
+
import { SyncStatusTracker, } from "./sync-status-tracker.js";
|
|
9
|
+
import { ChannelErrorSource } from "./types.js";
|
|
10
|
+
import { batchOperationsByDocument, createIdleHealth, filterOperations, toOperationWithContext, trimMailboxFromBatch, } from "./utils.js";
|
|
8
11
|
export class SyncManager {
|
|
9
12
|
logger;
|
|
10
13
|
remoteStorage;
|
|
11
14
|
cursorStorage;
|
|
15
|
+
deadLetterStorage;
|
|
12
16
|
channelFactory;
|
|
13
|
-
|
|
17
|
+
operationIndex;
|
|
14
18
|
reactor;
|
|
15
19
|
eventBus;
|
|
16
20
|
remotes;
|
|
17
21
|
awaiter;
|
|
22
|
+
syncAwaiter;
|
|
18
23
|
isShutdown;
|
|
19
24
|
eventUnsubscribe;
|
|
25
|
+
failedEventUnsubscribe;
|
|
26
|
+
batchAggregator;
|
|
27
|
+
syncStatusTracker;
|
|
28
|
+
maxDeadLettersPerRemote;
|
|
20
29
|
loadJobs = new Map();
|
|
21
|
-
constructor(logger, remoteStorage, cursorStorage, channelFactory, operationIndex, reactor, eventBus) {
|
|
30
|
+
constructor(logger, remoteStorage, cursorStorage, deadLetterStorage, channelFactory, operationIndex, reactor, eventBus, maxDeadLettersPerRemote = 100) {
|
|
22
31
|
this.logger = logger;
|
|
23
32
|
this.remoteStorage = remoteStorage;
|
|
24
33
|
this.cursorStorage = cursorStorage;
|
|
34
|
+
this.deadLetterStorage = deadLetterStorage;
|
|
25
35
|
this.channelFactory = channelFactory;
|
|
26
|
-
this.
|
|
36
|
+
this.operationIndex = operationIndex;
|
|
27
37
|
this.reactor = reactor;
|
|
28
38
|
this.eventBus = eventBus;
|
|
39
|
+
this.maxDeadLettersPerRemote = maxDeadLettersPerRemote;
|
|
29
40
|
this.remotes = new Map();
|
|
30
41
|
this.awaiter = new JobAwaiter(eventBus, (jobId, signal) => reactor.getJobStatus(jobId, signal));
|
|
42
|
+
this.syncAwaiter = new SyncAwaiter(eventBus);
|
|
31
43
|
this.isShutdown = false;
|
|
44
|
+
this.batchAggregator = new BatchAggregator(logger, (batch) => this.processCompleteBatch(batch));
|
|
45
|
+
this.syncStatusTracker = new SyncStatusTracker();
|
|
32
46
|
}
|
|
33
47
|
async startup() {
|
|
34
48
|
if (this.isShutdown) {
|
|
@@ -36,14 +50,7 @@ export class SyncManager {
|
|
|
36
50
|
}
|
|
37
51
|
const remoteRecords = await this.remoteStorage.list();
|
|
38
52
|
for (const record of remoteRecords) {
|
|
39
|
-
const channel = this.channelFactory.instance(record.id, record.name, record.channelConfig, this.cursorStorage, record.collectionId, record.filter);
|
|
40
|
-
try {
|
|
41
|
-
await channel.init();
|
|
42
|
-
}
|
|
43
|
-
catch (error) {
|
|
44
|
-
console.error(`Error initializing channel for remote ${record.name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
53
|
+
const channel = this.channelFactory.instance(record.id, record.name, record.channelConfig, this.cursorStorage, record.collectionId, record.filter, this.operationIndex);
|
|
47
54
|
const remote = {
|
|
48
55
|
id: record.id,
|
|
49
56
|
name: record.name,
|
|
@@ -53,28 +60,47 @@ export class SyncManager {
|
|
|
53
60
|
channel,
|
|
54
61
|
};
|
|
55
62
|
this.remotes.set(record.name, remote);
|
|
63
|
+
await this.loadDeadLetters(remote);
|
|
56
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
|
+
}
|
|
57
78
|
}
|
|
58
|
-
this.eventUnsubscribe = this.eventBus.subscribe(
|
|
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));
|
|
59
81
|
}
|
|
60
82
|
shutdown() {
|
|
61
83
|
this.isShutdown = true;
|
|
84
|
+
this.batchAggregator.clear();
|
|
62
85
|
if (this.eventUnsubscribe) {
|
|
63
86
|
this.eventUnsubscribe();
|
|
64
87
|
this.eventUnsubscribe = undefined;
|
|
65
88
|
}
|
|
89
|
+
if (this.failedEventUnsubscribe) {
|
|
90
|
+
this.failedEventUnsubscribe();
|
|
91
|
+
this.failedEventUnsubscribe = undefined;
|
|
92
|
+
}
|
|
66
93
|
this.awaiter.shutdown();
|
|
94
|
+
this.syncAwaiter.shutdown();
|
|
95
|
+
this.syncStatusTracker.clear();
|
|
96
|
+
const promises = [];
|
|
67
97
|
for (const remote of this.remotes.values()) {
|
|
68
|
-
|
|
69
|
-
remote.channel.shutdown();
|
|
70
|
-
}
|
|
71
|
-
catch (error) {
|
|
72
|
-
console.error(`Error shutting down channel for remote ${remote.name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
73
|
-
}
|
|
98
|
+
promises.push(remote.channel.shutdown());
|
|
74
99
|
}
|
|
75
100
|
this.remotes.clear();
|
|
76
101
|
return {
|
|
77
102
|
isShutdown: true,
|
|
103
|
+
completed: Promise.all(promises).then(() => undefined),
|
|
78
104
|
};
|
|
79
105
|
}
|
|
80
106
|
getByName(name) {
|
|
@@ -92,7 +118,7 @@ export class SyncManager {
|
|
|
92
118
|
}
|
|
93
119
|
throw new Error(`Remote with id '${id}' does not exist`);
|
|
94
120
|
}
|
|
95
|
-
async add(name, collectionId, channelConfig, filter = { documentId: [], scope: [], branch: "" }, options = {}, id) {
|
|
121
|
+
async add(name, collectionId, channelConfig, filter = { documentId: [], scope: [], branch: "" }, options = { sinceTimestampUtcMs: "0" }, id) {
|
|
96
122
|
if (this.isShutdown) {
|
|
97
123
|
throw new Error("SyncManager is shutdown and cannot add remotes");
|
|
98
124
|
}
|
|
@@ -115,8 +141,7 @@ export class SyncManager {
|
|
|
115
141
|
status,
|
|
116
142
|
};
|
|
117
143
|
await this.remoteStorage.upsert(remoteRecord);
|
|
118
|
-
const channel = this.channelFactory.instance(remoteId, name, channelConfig, this.cursorStorage, collectionId, filter);
|
|
119
|
-
await channel.init();
|
|
144
|
+
const channel = this.channelFactory.instance(remoteId, name, channelConfig, this.cursorStorage, collectionId, filter, this.operationIndex);
|
|
120
145
|
const remote = {
|
|
121
146
|
id: remoteId,
|
|
122
147
|
name,
|
|
@@ -126,103 +151,152 @@ export class SyncManager {
|
|
|
126
151
|
channel,
|
|
127
152
|
};
|
|
128
153
|
this.remotes.set(name, remote);
|
|
154
|
+
await this.loadDeadLetters(remote);
|
|
129
155
|
this.wireChannelCallbacks(remote);
|
|
130
|
-
await this.backfillOutbox(remote, collectionId, filter);
|
|
131
|
-
return remote;
|
|
132
|
-
}
|
|
133
|
-
async backfillOutbox(remote, collectionId, filter) {
|
|
134
|
-
let historicalOps;
|
|
135
156
|
try {
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
catch {
|
|
139
|
-
return;
|
|
157
|
+
await channel.init();
|
|
140
158
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
operation: {
|
|
146
|
-
id: entry.id,
|
|
147
|
-
index: entry.index,
|
|
148
|
-
skip: entry.skip,
|
|
149
|
-
hash: entry.hash,
|
|
150
|
-
timestampUtcMs: entry.timestampUtcMs,
|
|
151
|
-
action: entry.action,
|
|
152
|
-
},
|
|
153
|
-
context: {
|
|
154
|
-
documentId: entry.documentId,
|
|
155
|
-
documentType: entry.documentType,
|
|
156
|
-
scope: entry.scope,
|
|
157
|
-
branch: entry.branch,
|
|
158
|
-
ordinal: entry.ordinal ?? 0,
|
|
159
|
-
},
|
|
160
|
-
}));
|
|
161
|
-
const filteredOps = filterOperations(opsWithContext, filter);
|
|
162
|
-
if (filteredOps.length === 0) {
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
const batches = batchOperationsByDocument(filteredOps);
|
|
166
|
-
for (const batch of batches) {
|
|
167
|
-
const syncOp = new SyncOperation(crypto.randomUUID(), remote.name, batch.documentId, [batch.scope], batch.branch, batch.operations);
|
|
168
|
-
remote.channel.outbox.add(syncOp);
|
|
159
|
+
catch (error) {
|
|
160
|
+
this.remotes.delete(name);
|
|
161
|
+
await this.remoteStorage.remove(name);
|
|
162
|
+
throw error;
|
|
169
163
|
}
|
|
164
|
+
// backfill
|
|
165
|
+
await this.updateOutbox(remote, 0);
|
|
166
|
+
return remote;
|
|
170
167
|
}
|
|
171
168
|
async remove(name) {
|
|
172
169
|
const remote = this.remotes.get(name);
|
|
173
170
|
if (!remote) {
|
|
174
171
|
throw new Error(`Remote with name '${name}' does not exist`);
|
|
175
172
|
}
|
|
173
|
+
// shutdown the channel
|
|
174
|
+
await remote.channel.shutdown();
|
|
175
|
+
// delete the remote's data
|
|
176
176
|
await this.remoteStorage.remove(name);
|
|
177
|
-
|
|
177
|
+
await this.cursorStorage.remove(name);
|
|
178
|
+
this.syncStatusTracker.untrackRemote(name);
|
|
178
179
|
this.remotes.delete(name);
|
|
179
180
|
}
|
|
180
181
|
list() {
|
|
181
182
|
return Array.from(this.remotes.values());
|
|
182
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
|
+
}
|
|
183
193
|
wireChannelCallbacks(remote) {
|
|
184
|
-
remote.channel.inbox.onAdded((
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
+
}
|
|
189
222
|
});
|
|
190
223
|
}
|
|
191
|
-
|
|
192
|
-
|
|
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));
|
|
193
235
|
return;
|
|
194
236
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
+
}
|
|
208
269
|
}
|
|
209
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
|
+
}
|
|
210
279
|
}
|
|
211
|
-
|
|
280
|
+
handleInboxAdded(remote, syncOps) {
|
|
212
281
|
if (this.isShutdown) {
|
|
213
282
|
return;
|
|
214
283
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
remote.channel.outbox.remove(syncOp);
|
|
284
|
+
const keyed = [];
|
|
285
|
+
const nonKeyed = [];
|
|
286
|
+
for (const syncOp of syncOps) {
|
|
287
|
+
if (syncOp.jobId) {
|
|
288
|
+
keyed.push(syncOp);
|
|
221
289
|
}
|
|
222
|
-
else
|
|
223
|
-
|
|
290
|
+
else {
|
|
291
|
+
nonKeyed.push(syncOp);
|
|
224
292
|
}
|
|
225
|
-
}
|
|
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
|
+
}
|
|
226
300
|
}
|
|
227
301
|
async applyInboxJob(remote, syncOp) {
|
|
228
302
|
const operations = syncOp.operations.map((op) => op.operation);
|
|
@@ -232,6 +306,7 @@ export class SyncManager {
|
|
|
232
306
|
}
|
|
233
307
|
catch (error) {
|
|
234
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);
|
|
235
310
|
const channelError = new ChannelError(ChannelErrorSource.Inbox, err);
|
|
236
311
|
syncOp.failed(channelError);
|
|
237
312
|
remote.channel.deadLetter.add(syncOp);
|
|
@@ -244,6 +319,7 @@ export class SyncManager {
|
|
|
244
319
|
}
|
|
245
320
|
catch (error) {
|
|
246
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);
|
|
247
323
|
const channelError = new ChannelError(ChannelErrorSource.Inbox, err);
|
|
248
324
|
syncOp.failed(channelError);
|
|
249
325
|
remote.channel.deadLetter.add(syncOp);
|
|
@@ -253,7 +329,9 @@ export class SyncManager {
|
|
|
253
329
|
const jobKey = `${syncOp.documentId}:${syncOp.branch}`;
|
|
254
330
|
this.loadJobs.set(jobKey, completedJobInfo);
|
|
255
331
|
if (completedJobInfo.status === JobStatus.FAILED) {
|
|
256
|
-
const
|
|
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}`));
|
|
257
335
|
syncOp.failed(error);
|
|
258
336
|
remote.channel.deadLetter.add(syncOp);
|
|
259
337
|
}
|
|
@@ -262,5 +340,108 @@ export class SyncManager {
|
|
|
262
340
|
}
|
|
263
341
|
remote.channel.inbox.remove(syncOp);
|
|
264
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
|
+
}
|
|
265
446
|
}
|
|
266
447
|
//# sourceMappingURL=sync-manager.js.map
|