@powerhousedao/connect 6.0.0-dev.90 → 6.0.0-dev.91
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/context/index.js +112 -39
- package/dist/src/hooks/index.js +112 -39
- package/dist/src/main.js +112 -39
- package/dist/src/pages/index.js +112 -39
- package/dist/src/store/index.js +112 -39
- package/dist/src/utils/index.js +112 -39
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +12 -12
package/dist/src/main.js
CHANGED
|
@@ -7982,7 +7982,7 @@ var init_package = __esm(() => {
|
|
|
7982
7982
|
package_default = {
|
|
7983
7983
|
name: "@powerhousedao/connect",
|
|
7984
7984
|
productName: "Powerhouse-Connect",
|
|
7985
|
-
version: "6.0.0-dev.
|
|
7985
|
+
version: "6.0.0-dev.90",
|
|
7986
7986
|
description: "Powerhouse Connect",
|
|
7987
7987
|
main: "dist/index.html",
|
|
7988
7988
|
type: "module",
|
|
@@ -19440,6 +19440,7 @@ class GqlRequestChannel {
|
|
|
19440
19440
|
cursorStorage;
|
|
19441
19441
|
operationIndex;
|
|
19442
19442
|
pollTimer;
|
|
19443
|
+
abortController = new AbortController;
|
|
19443
19444
|
isShutdown;
|
|
19444
19445
|
failureCount;
|
|
19445
19446
|
lastSuccessUtcMs;
|
|
@@ -19520,6 +19521,7 @@ class GqlRequestChannel {
|
|
|
19520
19521
|
});
|
|
19521
19522
|
}
|
|
19522
19523
|
shutdown() {
|
|
19524
|
+
this.abortController.abort();
|
|
19523
19525
|
this.bufferedOutbox.flush();
|
|
19524
19526
|
this.isShutdown = true;
|
|
19525
19527
|
this.pollTimer.stop();
|
|
@@ -19547,7 +19549,7 @@ class GqlRequestChannel {
|
|
|
19547
19549
|
};
|
|
19548
19550
|
}
|
|
19549
19551
|
async init() {
|
|
19550
|
-
await this.touchRemoteChannel();
|
|
19552
|
+
const { ackOrdinal } = await this.touchRemoteChannel();
|
|
19551
19553
|
const cursors = await this.cursorStorage.list(this.remoteName);
|
|
19552
19554
|
const inboxOrdinal = cursors.find((c2) => c2.cursorType === "inbox")?.cursorOrdinal ?? 0;
|
|
19553
19555
|
const outboxOrdinal = cursors.find((c2) => c2.cursorType === "outbox")?.cursorOrdinal ?? 0;
|
|
@@ -19555,6 +19557,9 @@ class GqlRequestChannel {
|
|
|
19555
19557
|
this.outbox.init(outboxOrdinal);
|
|
19556
19558
|
this.lastPersistedInboxOrdinal = inboxOrdinal;
|
|
19557
19559
|
this.lastPersistedOutboxOrdinal = outboxOrdinal;
|
|
19560
|
+
if (ackOrdinal > 0) {
|
|
19561
|
+
trimMailboxFromAckOrdinal(this.outbox, ackOrdinal);
|
|
19562
|
+
}
|
|
19558
19563
|
this.pollTimer.setDelegate(() => this.poll());
|
|
19559
19564
|
this.pollTimer.start();
|
|
19560
19565
|
this.transitionConnectionState("connected");
|
|
@@ -19624,34 +19629,57 @@ class GqlRequestChannel {
|
|
|
19624
19629
|
this.deadLetter.add(...syncOps);
|
|
19625
19630
|
}
|
|
19626
19631
|
handlePollError(error) {
|
|
19632
|
+
if (this.isShutdown)
|
|
19633
|
+
return true;
|
|
19627
19634
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
19628
19635
|
if (err.message.includes("Channel not found")) {
|
|
19629
19636
|
this.transitionConnectionState("reconnecting");
|
|
19630
19637
|
this.recoverFromChannelNotFound();
|
|
19631
19638
|
return true;
|
|
19632
19639
|
}
|
|
19640
|
+
const classification = this.classifyError(err);
|
|
19633
19641
|
this.failureCount++;
|
|
19634
19642
|
this.lastFailureUtcMs = Date.now();
|
|
19635
|
-
this.transitionConnectionState("error");
|
|
19636
19643
|
const channelError = new ChannelError("inbox", err);
|
|
19637
|
-
this.logger.error("GqlChannel poll error (@FailureCount): @Error", this.failureCount, channelError);
|
|
19644
|
+
this.logger.error("GqlChannel poll error (@FailureCount, @Classification): @Error", this.failureCount, classification, channelError);
|
|
19645
|
+
if (classification === "unrecoverable") {
|
|
19646
|
+
this.pollTimer.stop();
|
|
19647
|
+
this.transitionConnectionState("error");
|
|
19648
|
+
return true;
|
|
19649
|
+
}
|
|
19650
|
+
this.transitionConnectionState("error");
|
|
19638
19651
|
return false;
|
|
19639
19652
|
}
|
|
19640
19653
|
recoverFromChannelNotFound() {
|
|
19641
19654
|
this.logger.info("GqlChannel @ChannelId not found on remote, re-registering...", this.channelId);
|
|
19642
19655
|
this.pollTimer.stop();
|
|
19643
|
-
|
|
19644
|
-
|
|
19645
|
-
|
|
19646
|
-
this.
|
|
19647
|
-
|
|
19648
|
-
|
|
19649
|
-
|
|
19650
|
-
|
|
19651
|
-
|
|
19652
|
-
|
|
19653
|
-
|
|
19654
|
-
|
|
19656
|
+
const attemptRecovery = (attempt) => {
|
|
19657
|
+
if (this.isShutdown)
|
|
19658
|
+
return;
|
|
19659
|
+
this.touchRemoteChannel().then(({ ackOrdinal }) => {
|
|
19660
|
+
this.logger.info("GqlChannel @ChannelId re-registered successfully", this.channelId);
|
|
19661
|
+
this.failureCount = 0;
|
|
19662
|
+
if (ackOrdinal > 0) {
|
|
19663
|
+
trimMailboxFromAckOrdinal(this.outbox, ackOrdinal);
|
|
19664
|
+
}
|
|
19665
|
+
this.pollTimer.start();
|
|
19666
|
+
this.transitionConnectionState("connected");
|
|
19667
|
+
}).catch((recoveryError) => {
|
|
19668
|
+
const err = recoveryError instanceof Error ? recoveryError : new Error(String(recoveryError));
|
|
19669
|
+
const classification = this.classifyError(err);
|
|
19670
|
+
this.logger.error("GqlChannel @ChannelId recovery attempt @Attempt failed (@Classification): @Error", this.channelId, attempt, classification, recoveryError);
|
|
19671
|
+
this.failureCount++;
|
|
19672
|
+
this.lastFailureUtcMs = Date.now();
|
|
19673
|
+
if (classification === "unrecoverable") {
|
|
19674
|
+
this.transitionConnectionState("error");
|
|
19675
|
+
return;
|
|
19676
|
+
}
|
|
19677
|
+
this.transitionConnectionState("reconnecting");
|
|
19678
|
+
const delay = calculateBackoffDelay(attempt, this.config.retryBaseDelayMs, this.config.retryMaxDelayMs, Math.random());
|
|
19679
|
+
setTimeout(() => attemptRecovery(attempt + 1), delay);
|
|
19680
|
+
});
|
|
19681
|
+
};
|
|
19682
|
+
attemptRecovery(1);
|
|
19655
19683
|
}
|
|
19656
19684
|
async pollSyncEnvelopes(ackOrdinal, latestOrdinal) {
|
|
19657
19685
|
const query = `
|
|
@@ -19749,7 +19777,10 @@ class GqlRequestChannel {
|
|
|
19749
19777
|
} catch {}
|
|
19750
19778
|
const mutation = `
|
|
19751
19779
|
mutation TouchChannel($input: TouchChannelInput!) {
|
|
19752
|
-
touchChannel(input: $input)
|
|
19780
|
+
touchChannel(input: $input) {
|
|
19781
|
+
success
|
|
19782
|
+
ackOrdinal
|
|
19783
|
+
}
|
|
19753
19784
|
}
|
|
19754
19785
|
`;
|
|
19755
19786
|
const variables = {
|
|
@@ -19765,7 +19796,11 @@ class GqlRequestChannel {
|
|
|
19765
19796
|
sinceTimestampUtcMs
|
|
19766
19797
|
}
|
|
19767
19798
|
};
|
|
19768
|
-
await this.executeGraphQL(mutation, variables);
|
|
19799
|
+
const data = await this.executeGraphQL(mutation, variables);
|
|
19800
|
+
if (!data.touchChannel.success) {
|
|
19801
|
+
throw new GraphQLRequestError("touchChannel returned success=false", "graphql");
|
|
19802
|
+
}
|
|
19803
|
+
return { ackOrdinal: data.touchChannel.ackOrdinal };
|
|
19769
19804
|
}
|
|
19770
19805
|
attemptPush(syncOps) {
|
|
19771
19806
|
this.pushSyncOperations(syncOps).then(() => {
|
|
@@ -19775,8 +19810,11 @@ class GqlRequestChannel {
|
|
|
19775
19810
|
this.transitionConnectionState("connected");
|
|
19776
19811
|
}
|
|
19777
19812
|
}).catch((error) => {
|
|
19813
|
+
if (this.isShutdown)
|
|
19814
|
+
return;
|
|
19778
19815
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
19779
|
-
|
|
19816
|
+
const classification = this.classifyError(err);
|
|
19817
|
+
if (classification === "recoverable") {
|
|
19780
19818
|
this.pushFailureCount++;
|
|
19781
19819
|
this.pushBlocked = true;
|
|
19782
19820
|
this.logger.error("GqlChannel push failed (attempt @FailureCount), will retry: @Error", this.pushFailureCount, err);
|
|
@@ -19810,12 +19848,26 @@ class GqlRequestChannel {
|
|
|
19810
19848
|
this.attemptPush([...allItems]);
|
|
19811
19849
|
}, delay);
|
|
19812
19850
|
}
|
|
19813
|
-
|
|
19814
|
-
if (error
|
|
19815
|
-
return
|
|
19816
|
-
|
|
19817
|
-
|
|
19818
|
-
|
|
19851
|
+
classifyError(error) {
|
|
19852
|
+
if (!(error instanceof GraphQLRequestError)) {
|
|
19853
|
+
return "recoverable";
|
|
19854
|
+
}
|
|
19855
|
+
switch (error.category) {
|
|
19856
|
+
case "network":
|
|
19857
|
+
return "recoverable";
|
|
19858
|
+
case "http": {
|
|
19859
|
+
if (error.statusCode !== undefined && error.statusCode >= 500) {
|
|
19860
|
+
return "recoverable";
|
|
19861
|
+
}
|
|
19862
|
+
return "unrecoverable";
|
|
19863
|
+
}
|
|
19864
|
+
case "parse":
|
|
19865
|
+
return "recoverable";
|
|
19866
|
+
case "graphql":
|
|
19867
|
+
return "unrecoverable";
|
|
19868
|
+
case "missing-data":
|
|
19869
|
+
return "unrecoverable";
|
|
19870
|
+
}
|
|
19819
19871
|
}
|
|
19820
19872
|
async pushSyncOperations(syncOps) {
|
|
19821
19873
|
for (const syncOp of syncOps) {
|
|
@@ -19892,26 +19944,27 @@ class GqlRequestChannel {
|
|
|
19892
19944
|
body: JSON.stringify({
|
|
19893
19945
|
query,
|
|
19894
19946
|
variables
|
|
19895
|
-
})
|
|
19947
|
+
}),
|
|
19948
|
+
signal: this.abortController.signal
|
|
19896
19949
|
});
|
|
19897
19950
|
} catch (error) {
|
|
19898
|
-
throw new
|
|
19951
|
+
throw new GraphQLRequestError(`GraphQL request failed: ${error instanceof Error ? error.message : String(error)}`, "network");
|
|
19899
19952
|
}
|
|
19900
19953
|
if (!response.ok) {
|
|
19901
|
-
throw new
|
|
19954
|
+
throw new GraphQLRequestError(`GraphQL request failed: ${response.status} ${response.statusText}`, "http", response.status);
|
|
19902
19955
|
}
|
|
19903
19956
|
let result;
|
|
19904
19957
|
try {
|
|
19905
19958
|
result = await response.json();
|
|
19906
19959
|
} catch (error) {
|
|
19907
|
-
throw new
|
|
19960
|
+
throw new GraphQLRequestError(`Failed to parse GraphQL response: ${error instanceof Error ? error.message : String(error)}`, "parse");
|
|
19908
19961
|
}
|
|
19909
19962
|
this.logger.verbose("GQL response @channelId @operation status=@status data=@data errors=@errors", this.channelId, operationName, response.status, JSON.stringify(result.data), result.errors ? JSON.stringify(result.errors) : "none");
|
|
19910
19963
|
if (result.errors) {
|
|
19911
|
-
throw new
|
|
19964
|
+
throw new GraphQLRequestError(`GraphQL errors: ${JSON.stringify(result.errors, null, 2)}`, "graphql");
|
|
19912
19965
|
}
|
|
19913
19966
|
if (!result.data) {
|
|
19914
|
-
throw new
|
|
19967
|
+
throw new GraphQLRequestError("GraphQL response missing data field", "missing-data");
|
|
19915
19968
|
}
|
|
19916
19969
|
return result.data;
|
|
19917
19970
|
}
|
|
@@ -20831,6 +20884,7 @@ class SyncManager {
|
|
|
20831
20884
|
remotes;
|
|
20832
20885
|
awaiter;
|
|
20833
20886
|
syncAwaiter;
|
|
20887
|
+
abortController = new AbortController;
|
|
20834
20888
|
isShutdown;
|
|
20835
20889
|
eventUnsubscribe;
|
|
20836
20890
|
failedEventUnsubscribe;
|
|
@@ -20891,6 +20945,7 @@ class SyncManager {
|
|
|
20891
20945
|
}
|
|
20892
20946
|
shutdown() {
|
|
20893
20947
|
this.isShutdown = true;
|
|
20948
|
+
this.abortController.abort();
|
|
20894
20949
|
this.batchAggregator.clear();
|
|
20895
20950
|
if (this.eventUnsubscribe) {
|
|
20896
20951
|
this.eventUnsubscribe();
|
|
@@ -21083,6 +21138,8 @@ class SyncManager {
|
|
|
21083
21138
|
return Array.from(this.remotes.values()).filter((remote) => remote.collectionId === collectionId);
|
|
21084
21139
|
}
|
|
21085
21140
|
async processCompleteBatch(batch) {
|
|
21141
|
+
if (this.isShutdown)
|
|
21142
|
+
return;
|
|
21086
21143
|
const collectionIds = [
|
|
21087
21144
|
...new Set(Object.values(batch.collectionMemberships).flatMap((collections) => collections))
|
|
21088
21145
|
];
|
|
@@ -21126,8 +21183,10 @@ class SyncManager {
|
|
|
21126
21183
|
const operations = syncOp.operations.map((op) => op.operation);
|
|
21127
21184
|
let jobInfo;
|
|
21128
21185
|
try {
|
|
21129
|
-
jobInfo = await this.reactor.load(syncOp.documentId, syncOp.branch, operations,
|
|
21186
|
+
jobInfo = await this.reactor.load(syncOp.documentId, syncOp.branch, operations, this.abortController.signal, { sourceRemote: remote.name });
|
|
21130
21187
|
} catch (error) {
|
|
21188
|
+
if (this.isShutdown)
|
|
21189
|
+
return;
|
|
21131
21190
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
21132
21191
|
this.logger.error("Failed to load operations from inbox (@remote, @documentId, @error)", remote.name, syncOp.documentId, err.message);
|
|
21133
21192
|
const channelError = new ChannelError("inbox", err);
|
|
@@ -21138,8 +21197,10 @@ class SyncManager {
|
|
|
21138
21197
|
}
|
|
21139
21198
|
let completedJobInfo;
|
|
21140
21199
|
try {
|
|
21141
|
-
completedJobInfo = await this.awaiter.waitForJob(jobInfo.id);
|
|
21200
|
+
completedJobInfo = await this.awaiter.waitForJob(jobInfo.id, this.abortController.signal);
|
|
21142
21201
|
} catch (error) {
|
|
21202
|
+
if (this.isShutdown)
|
|
21203
|
+
return;
|
|
21143
21204
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
21144
21205
|
this.logger.error("Failed to wait for job completion (@remote, @documentId, @jobId, @error)", remote.name, syncOp.documentId, jobInfo.id, err.message);
|
|
21145
21206
|
const channelError = new ChannelError("inbox", err);
|
|
@@ -21174,10 +21235,10 @@ class SyncManager {
|
|
|
21174
21235
|
const request = { jobs };
|
|
21175
21236
|
let result;
|
|
21176
21237
|
try {
|
|
21177
|
-
result = await this.reactor.loadBatch(request,
|
|
21178
|
-
sourceRemote
|
|
21179
|
-
});
|
|
21238
|
+
result = await this.reactor.loadBatch(request, this.abortController.signal, { sourceRemote });
|
|
21180
21239
|
} catch (error) {
|
|
21240
|
+
if (this.isShutdown)
|
|
21241
|
+
return;
|
|
21181
21242
|
for (const { remote, syncOp } of items) {
|
|
21182
21243
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
21183
21244
|
syncOp.failed(new ChannelError("inbox", err));
|
|
@@ -21198,8 +21259,10 @@ class SyncManager {
|
|
|
21198
21259
|
const jobInfo = result.jobs[syncOp.jobId];
|
|
21199
21260
|
let completedJobInfo;
|
|
21200
21261
|
try {
|
|
21201
|
-
completedJobInfo = await this.awaiter.waitForJob(jobInfo.id);
|
|
21262
|
+
completedJobInfo = await this.awaiter.waitForJob(jobInfo.id, this.abortController.signal);
|
|
21202
21263
|
} catch (error) {
|
|
21264
|
+
if (this.isShutdown)
|
|
21265
|
+
continue;
|
|
21203
21266
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
21204
21267
|
syncOp.failed(new ChannelError("inbox", err));
|
|
21205
21268
|
remote.channel.deadLetter.add(syncOp);
|
|
@@ -21246,7 +21309,7 @@ class SyncManager {
|
|
|
21246
21309
|
remote.channel.outbox.add(...syncOps);
|
|
21247
21310
|
}
|
|
21248
21311
|
async getOperationsForRemote(remote, ackOrdinal) {
|
|
21249
|
-
const results = await this.operationIndex.find(remote.collectionId, ackOrdinal, { excludeSourceRemote: remote.name });
|
|
21312
|
+
const results = await this.operationIndex.find(remote.collectionId, ackOrdinal, { excludeSourceRemote: remote.name }, undefined, this.abortController.signal);
|
|
21250
21313
|
let operations = results.results.map((entry) => toOperationWithContext(entry));
|
|
21251
21314
|
const sinceTimestamp = remote.options.sinceTimestampUtcMs;
|
|
21252
21315
|
if (sinceTimestamp && sinceTimestamp !== "0") {
|
|
@@ -22569,7 +22632,7 @@ var __defProp2, __export2 = (target, all) => {
|
|
|
22569
22632
|
}
|
|
22570
22633
|
}, DocumentDeletedError, InvalidSignatureError, DowngradeNotSupportedError, DocumentNotFoundError, getNextIndexForScope = (document2, scope) => {
|
|
22571
22634
|
return document2.header.revision[scope] || 0;
|
|
22572
|
-
}, EventBusAggregateError, ReactorEventTypes, QueueEventTypes, ModuleNotFoundError, DuplicateModuleError, DuplicateManifestError, ManifestNotFoundError, DowngradeNotSupportedError2, MissingUpgradeTransitionError, InvalidUpgradeStepError, STRICT_ORDER_ACTION_TYPES, MAX_SKIP_THRESHOLD = 1000, documentScopeActions, DRIVE_DOCUMENT_TYPE = "powerhouse/document-drive", ProcessorManager, KyselyDocumentView, KyselyDocumentIndexer, DuplicateOperationError, RevisionMismatchError, exports_001_create_operation_table, exports_002_create_keyframe_table, exports_003_create_document_table, exports_004_create_document_relationship_table, exports_005_create_indexer_state_table, exports_006_create_document_snapshot_table, exports_007_create_slug_mapping_table, exports_008_create_view_state_table, exports_009_create_operation_index_tables, exports_010_create_sync_tables, exports_011_add_cursor_type_column, exports_012_add_source_remote_column, exports_013_create_sync_dead_letters_table, exports_014_create_processor_cursor_table, REACTOR_SCHEMA = "reactor", migrations, ChannelScheme, SyncOperationStatus, ChannelErrorSource, SyncEventTypes, MailboxAggregateError, ChannelError, SyncOperationAggregateError, DEFAULT_CONFIG, syncOpCounter = 0, getLatestAppliedOrdinal = (syncOps) => {
|
|
22635
|
+
}, EventBusAggregateError, ReactorEventTypes, QueueEventTypes, ModuleNotFoundError, DuplicateModuleError, DuplicateManifestError, ManifestNotFoundError, DowngradeNotSupportedError2, MissingUpgradeTransitionError, InvalidUpgradeStepError, STRICT_ORDER_ACTION_TYPES, MAX_SKIP_THRESHOLD = 1000, documentScopeActions, DRIVE_DOCUMENT_TYPE = "powerhouse/document-drive", ProcessorManager, KyselyDocumentView, KyselyDocumentIndexer, DuplicateOperationError, RevisionMismatchError, exports_001_create_operation_table, exports_002_create_keyframe_table, exports_003_create_document_table, exports_004_create_document_relationship_table, exports_005_create_indexer_state_table, exports_006_create_document_snapshot_table, exports_007_create_slug_mapping_table, exports_008_create_view_state_table, exports_009_create_operation_index_tables, exports_010_create_sync_tables, exports_011_add_cursor_type_column, exports_012_add_source_remote_column, exports_013_create_sync_dead_letters_table, exports_014_create_processor_cursor_table, REACTOR_SCHEMA = "reactor", migrations, ChannelScheme, SyncOperationStatus, ChannelErrorSource, SyncEventTypes, MailboxAggregateError, GraphQLRequestError, ChannelError, SyncOperationAggregateError, DEFAULT_CONFIG, syncOpCounter = 0, getLatestAppliedOrdinal = (syncOps) => {
|
|
22573
22636
|
let maxOrdinal = 0;
|
|
22574
22637
|
for (const syncOp of syncOps) {
|
|
22575
22638
|
if (syncOp.status === 2) {
|
|
@@ -27777,6 +27840,16 @@ var init_src = __esm(() => {
|
|
|
27777
27840
|
this.errors = errors2;
|
|
27778
27841
|
}
|
|
27779
27842
|
};
|
|
27843
|
+
GraphQLRequestError = class GraphQLRequestError extends Error {
|
|
27844
|
+
statusCode;
|
|
27845
|
+
category;
|
|
27846
|
+
constructor(message, category, statusCode) {
|
|
27847
|
+
super(message);
|
|
27848
|
+
this.name = "GraphQLRequestError";
|
|
27849
|
+
this.category = category;
|
|
27850
|
+
this.statusCode = statusCode;
|
|
27851
|
+
}
|
|
27852
|
+
};
|
|
27780
27853
|
ChannelError = class ChannelError extends Error {
|
|
27781
27854
|
source;
|
|
27782
27855
|
error;
|
package/dist/src/pages/index.js
CHANGED
|
@@ -7982,7 +7982,7 @@ var init_package = __esm(() => {
|
|
|
7982
7982
|
package_default = {
|
|
7983
7983
|
name: "@powerhousedao/connect",
|
|
7984
7984
|
productName: "Powerhouse-Connect",
|
|
7985
|
-
version: "6.0.0-dev.
|
|
7985
|
+
version: "6.0.0-dev.90",
|
|
7986
7986
|
description: "Powerhouse Connect",
|
|
7987
7987
|
main: "dist/index.html",
|
|
7988
7988
|
type: "module",
|
|
@@ -19440,6 +19440,7 @@ class GqlRequestChannel {
|
|
|
19440
19440
|
cursorStorage;
|
|
19441
19441
|
operationIndex;
|
|
19442
19442
|
pollTimer;
|
|
19443
|
+
abortController = new AbortController;
|
|
19443
19444
|
isShutdown;
|
|
19444
19445
|
failureCount;
|
|
19445
19446
|
lastSuccessUtcMs;
|
|
@@ -19520,6 +19521,7 @@ class GqlRequestChannel {
|
|
|
19520
19521
|
});
|
|
19521
19522
|
}
|
|
19522
19523
|
shutdown() {
|
|
19524
|
+
this.abortController.abort();
|
|
19523
19525
|
this.bufferedOutbox.flush();
|
|
19524
19526
|
this.isShutdown = true;
|
|
19525
19527
|
this.pollTimer.stop();
|
|
@@ -19547,7 +19549,7 @@ class GqlRequestChannel {
|
|
|
19547
19549
|
};
|
|
19548
19550
|
}
|
|
19549
19551
|
async init() {
|
|
19550
|
-
await this.touchRemoteChannel();
|
|
19552
|
+
const { ackOrdinal } = await this.touchRemoteChannel();
|
|
19551
19553
|
const cursors = await this.cursorStorage.list(this.remoteName);
|
|
19552
19554
|
const inboxOrdinal = cursors.find((c2) => c2.cursorType === "inbox")?.cursorOrdinal ?? 0;
|
|
19553
19555
|
const outboxOrdinal = cursors.find((c2) => c2.cursorType === "outbox")?.cursorOrdinal ?? 0;
|
|
@@ -19555,6 +19557,9 @@ class GqlRequestChannel {
|
|
|
19555
19557
|
this.outbox.init(outboxOrdinal);
|
|
19556
19558
|
this.lastPersistedInboxOrdinal = inboxOrdinal;
|
|
19557
19559
|
this.lastPersistedOutboxOrdinal = outboxOrdinal;
|
|
19560
|
+
if (ackOrdinal > 0) {
|
|
19561
|
+
trimMailboxFromAckOrdinal(this.outbox, ackOrdinal);
|
|
19562
|
+
}
|
|
19558
19563
|
this.pollTimer.setDelegate(() => this.poll());
|
|
19559
19564
|
this.pollTimer.start();
|
|
19560
19565
|
this.transitionConnectionState("connected");
|
|
@@ -19624,34 +19629,57 @@ class GqlRequestChannel {
|
|
|
19624
19629
|
this.deadLetter.add(...syncOps);
|
|
19625
19630
|
}
|
|
19626
19631
|
handlePollError(error) {
|
|
19632
|
+
if (this.isShutdown)
|
|
19633
|
+
return true;
|
|
19627
19634
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
19628
19635
|
if (err.message.includes("Channel not found")) {
|
|
19629
19636
|
this.transitionConnectionState("reconnecting");
|
|
19630
19637
|
this.recoverFromChannelNotFound();
|
|
19631
19638
|
return true;
|
|
19632
19639
|
}
|
|
19640
|
+
const classification = this.classifyError(err);
|
|
19633
19641
|
this.failureCount++;
|
|
19634
19642
|
this.lastFailureUtcMs = Date.now();
|
|
19635
|
-
this.transitionConnectionState("error");
|
|
19636
19643
|
const channelError = new ChannelError("inbox", err);
|
|
19637
|
-
this.logger.error("GqlChannel poll error (@FailureCount): @Error", this.failureCount, channelError);
|
|
19644
|
+
this.logger.error("GqlChannel poll error (@FailureCount, @Classification): @Error", this.failureCount, classification, channelError);
|
|
19645
|
+
if (classification === "unrecoverable") {
|
|
19646
|
+
this.pollTimer.stop();
|
|
19647
|
+
this.transitionConnectionState("error");
|
|
19648
|
+
return true;
|
|
19649
|
+
}
|
|
19650
|
+
this.transitionConnectionState("error");
|
|
19638
19651
|
return false;
|
|
19639
19652
|
}
|
|
19640
19653
|
recoverFromChannelNotFound() {
|
|
19641
19654
|
this.logger.info("GqlChannel @ChannelId not found on remote, re-registering...", this.channelId);
|
|
19642
19655
|
this.pollTimer.stop();
|
|
19643
|
-
|
|
19644
|
-
|
|
19645
|
-
|
|
19646
|
-
this.
|
|
19647
|
-
|
|
19648
|
-
|
|
19649
|
-
|
|
19650
|
-
|
|
19651
|
-
|
|
19652
|
-
|
|
19653
|
-
|
|
19654
|
-
|
|
19656
|
+
const attemptRecovery = (attempt) => {
|
|
19657
|
+
if (this.isShutdown)
|
|
19658
|
+
return;
|
|
19659
|
+
this.touchRemoteChannel().then(({ ackOrdinal }) => {
|
|
19660
|
+
this.logger.info("GqlChannel @ChannelId re-registered successfully", this.channelId);
|
|
19661
|
+
this.failureCount = 0;
|
|
19662
|
+
if (ackOrdinal > 0) {
|
|
19663
|
+
trimMailboxFromAckOrdinal(this.outbox, ackOrdinal);
|
|
19664
|
+
}
|
|
19665
|
+
this.pollTimer.start();
|
|
19666
|
+
this.transitionConnectionState("connected");
|
|
19667
|
+
}).catch((recoveryError) => {
|
|
19668
|
+
const err = recoveryError instanceof Error ? recoveryError : new Error(String(recoveryError));
|
|
19669
|
+
const classification = this.classifyError(err);
|
|
19670
|
+
this.logger.error("GqlChannel @ChannelId recovery attempt @Attempt failed (@Classification): @Error", this.channelId, attempt, classification, recoveryError);
|
|
19671
|
+
this.failureCount++;
|
|
19672
|
+
this.lastFailureUtcMs = Date.now();
|
|
19673
|
+
if (classification === "unrecoverable") {
|
|
19674
|
+
this.transitionConnectionState("error");
|
|
19675
|
+
return;
|
|
19676
|
+
}
|
|
19677
|
+
this.transitionConnectionState("reconnecting");
|
|
19678
|
+
const delay = calculateBackoffDelay(attempt, this.config.retryBaseDelayMs, this.config.retryMaxDelayMs, Math.random());
|
|
19679
|
+
setTimeout(() => attemptRecovery(attempt + 1), delay);
|
|
19680
|
+
});
|
|
19681
|
+
};
|
|
19682
|
+
attemptRecovery(1);
|
|
19655
19683
|
}
|
|
19656
19684
|
async pollSyncEnvelopes(ackOrdinal, latestOrdinal) {
|
|
19657
19685
|
const query = `
|
|
@@ -19749,7 +19777,10 @@ class GqlRequestChannel {
|
|
|
19749
19777
|
} catch {}
|
|
19750
19778
|
const mutation = `
|
|
19751
19779
|
mutation TouchChannel($input: TouchChannelInput!) {
|
|
19752
|
-
touchChannel(input: $input)
|
|
19780
|
+
touchChannel(input: $input) {
|
|
19781
|
+
success
|
|
19782
|
+
ackOrdinal
|
|
19783
|
+
}
|
|
19753
19784
|
}
|
|
19754
19785
|
`;
|
|
19755
19786
|
const variables = {
|
|
@@ -19765,7 +19796,11 @@ class GqlRequestChannel {
|
|
|
19765
19796
|
sinceTimestampUtcMs
|
|
19766
19797
|
}
|
|
19767
19798
|
};
|
|
19768
|
-
await this.executeGraphQL(mutation, variables);
|
|
19799
|
+
const data = await this.executeGraphQL(mutation, variables);
|
|
19800
|
+
if (!data.touchChannel.success) {
|
|
19801
|
+
throw new GraphQLRequestError("touchChannel returned success=false", "graphql");
|
|
19802
|
+
}
|
|
19803
|
+
return { ackOrdinal: data.touchChannel.ackOrdinal };
|
|
19769
19804
|
}
|
|
19770
19805
|
attemptPush(syncOps) {
|
|
19771
19806
|
this.pushSyncOperations(syncOps).then(() => {
|
|
@@ -19775,8 +19810,11 @@ class GqlRequestChannel {
|
|
|
19775
19810
|
this.transitionConnectionState("connected");
|
|
19776
19811
|
}
|
|
19777
19812
|
}).catch((error) => {
|
|
19813
|
+
if (this.isShutdown)
|
|
19814
|
+
return;
|
|
19778
19815
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
19779
|
-
|
|
19816
|
+
const classification = this.classifyError(err);
|
|
19817
|
+
if (classification === "recoverable") {
|
|
19780
19818
|
this.pushFailureCount++;
|
|
19781
19819
|
this.pushBlocked = true;
|
|
19782
19820
|
this.logger.error("GqlChannel push failed (attempt @FailureCount), will retry: @Error", this.pushFailureCount, err);
|
|
@@ -19810,12 +19848,26 @@ class GqlRequestChannel {
|
|
|
19810
19848
|
this.attemptPush([...allItems]);
|
|
19811
19849
|
}, delay);
|
|
19812
19850
|
}
|
|
19813
|
-
|
|
19814
|
-
if (error
|
|
19815
|
-
return
|
|
19816
|
-
|
|
19817
|
-
|
|
19818
|
-
|
|
19851
|
+
classifyError(error) {
|
|
19852
|
+
if (!(error instanceof GraphQLRequestError)) {
|
|
19853
|
+
return "recoverable";
|
|
19854
|
+
}
|
|
19855
|
+
switch (error.category) {
|
|
19856
|
+
case "network":
|
|
19857
|
+
return "recoverable";
|
|
19858
|
+
case "http": {
|
|
19859
|
+
if (error.statusCode !== undefined && error.statusCode >= 500) {
|
|
19860
|
+
return "recoverable";
|
|
19861
|
+
}
|
|
19862
|
+
return "unrecoverable";
|
|
19863
|
+
}
|
|
19864
|
+
case "parse":
|
|
19865
|
+
return "recoverable";
|
|
19866
|
+
case "graphql":
|
|
19867
|
+
return "unrecoverable";
|
|
19868
|
+
case "missing-data":
|
|
19869
|
+
return "unrecoverable";
|
|
19870
|
+
}
|
|
19819
19871
|
}
|
|
19820
19872
|
async pushSyncOperations(syncOps) {
|
|
19821
19873
|
for (const syncOp of syncOps) {
|
|
@@ -19892,26 +19944,27 @@ class GqlRequestChannel {
|
|
|
19892
19944
|
body: JSON.stringify({
|
|
19893
19945
|
query,
|
|
19894
19946
|
variables
|
|
19895
|
-
})
|
|
19947
|
+
}),
|
|
19948
|
+
signal: this.abortController.signal
|
|
19896
19949
|
});
|
|
19897
19950
|
} catch (error) {
|
|
19898
|
-
throw new
|
|
19951
|
+
throw new GraphQLRequestError(`GraphQL request failed: ${error instanceof Error ? error.message : String(error)}`, "network");
|
|
19899
19952
|
}
|
|
19900
19953
|
if (!response.ok) {
|
|
19901
|
-
throw new
|
|
19954
|
+
throw new GraphQLRequestError(`GraphQL request failed: ${response.status} ${response.statusText}`, "http", response.status);
|
|
19902
19955
|
}
|
|
19903
19956
|
let result;
|
|
19904
19957
|
try {
|
|
19905
19958
|
result = await response.json();
|
|
19906
19959
|
} catch (error) {
|
|
19907
|
-
throw new
|
|
19960
|
+
throw new GraphQLRequestError(`Failed to parse GraphQL response: ${error instanceof Error ? error.message : String(error)}`, "parse");
|
|
19908
19961
|
}
|
|
19909
19962
|
this.logger.verbose("GQL response @channelId @operation status=@status data=@data errors=@errors", this.channelId, operationName, response.status, JSON.stringify(result.data), result.errors ? JSON.stringify(result.errors) : "none");
|
|
19910
19963
|
if (result.errors) {
|
|
19911
|
-
throw new
|
|
19964
|
+
throw new GraphQLRequestError(`GraphQL errors: ${JSON.stringify(result.errors, null, 2)}`, "graphql");
|
|
19912
19965
|
}
|
|
19913
19966
|
if (!result.data) {
|
|
19914
|
-
throw new
|
|
19967
|
+
throw new GraphQLRequestError("GraphQL response missing data field", "missing-data");
|
|
19915
19968
|
}
|
|
19916
19969
|
return result.data;
|
|
19917
19970
|
}
|
|
@@ -20831,6 +20884,7 @@ class SyncManager {
|
|
|
20831
20884
|
remotes;
|
|
20832
20885
|
awaiter;
|
|
20833
20886
|
syncAwaiter;
|
|
20887
|
+
abortController = new AbortController;
|
|
20834
20888
|
isShutdown;
|
|
20835
20889
|
eventUnsubscribe;
|
|
20836
20890
|
failedEventUnsubscribe;
|
|
@@ -20891,6 +20945,7 @@ class SyncManager {
|
|
|
20891
20945
|
}
|
|
20892
20946
|
shutdown() {
|
|
20893
20947
|
this.isShutdown = true;
|
|
20948
|
+
this.abortController.abort();
|
|
20894
20949
|
this.batchAggregator.clear();
|
|
20895
20950
|
if (this.eventUnsubscribe) {
|
|
20896
20951
|
this.eventUnsubscribe();
|
|
@@ -21083,6 +21138,8 @@ class SyncManager {
|
|
|
21083
21138
|
return Array.from(this.remotes.values()).filter((remote) => remote.collectionId === collectionId);
|
|
21084
21139
|
}
|
|
21085
21140
|
async processCompleteBatch(batch) {
|
|
21141
|
+
if (this.isShutdown)
|
|
21142
|
+
return;
|
|
21086
21143
|
const collectionIds = [
|
|
21087
21144
|
...new Set(Object.values(batch.collectionMemberships).flatMap((collections) => collections))
|
|
21088
21145
|
];
|
|
@@ -21126,8 +21183,10 @@ class SyncManager {
|
|
|
21126
21183
|
const operations = syncOp.operations.map((op) => op.operation);
|
|
21127
21184
|
let jobInfo;
|
|
21128
21185
|
try {
|
|
21129
|
-
jobInfo = await this.reactor.load(syncOp.documentId, syncOp.branch, operations,
|
|
21186
|
+
jobInfo = await this.reactor.load(syncOp.documentId, syncOp.branch, operations, this.abortController.signal, { sourceRemote: remote.name });
|
|
21130
21187
|
} catch (error) {
|
|
21188
|
+
if (this.isShutdown)
|
|
21189
|
+
return;
|
|
21131
21190
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
21132
21191
|
this.logger.error("Failed to load operations from inbox (@remote, @documentId, @error)", remote.name, syncOp.documentId, err.message);
|
|
21133
21192
|
const channelError = new ChannelError("inbox", err);
|
|
@@ -21138,8 +21197,10 @@ class SyncManager {
|
|
|
21138
21197
|
}
|
|
21139
21198
|
let completedJobInfo;
|
|
21140
21199
|
try {
|
|
21141
|
-
completedJobInfo = await this.awaiter.waitForJob(jobInfo.id);
|
|
21200
|
+
completedJobInfo = await this.awaiter.waitForJob(jobInfo.id, this.abortController.signal);
|
|
21142
21201
|
} catch (error) {
|
|
21202
|
+
if (this.isShutdown)
|
|
21203
|
+
return;
|
|
21143
21204
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
21144
21205
|
this.logger.error("Failed to wait for job completion (@remote, @documentId, @jobId, @error)", remote.name, syncOp.documentId, jobInfo.id, err.message);
|
|
21145
21206
|
const channelError = new ChannelError("inbox", err);
|
|
@@ -21174,10 +21235,10 @@ class SyncManager {
|
|
|
21174
21235
|
const request = { jobs };
|
|
21175
21236
|
let result;
|
|
21176
21237
|
try {
|
|
21177
|
-
result = await this.reactor.loadBatch(request,
|
|
21178
|
-
sourceRemote
|
|
21179
|
-
});
|
|
21238
|
+
result = await this.reactor.loadBatch(request, this.abortController.signal, { sourceRemote });
|
|
21180
21239
|
} catch (error) {
|
|
21240
|
+
if (this.isShutdown)
|
|
21241
|
+
return;
|
|
21181
21242
|
for (const { remote, syncOp } of items) {
|
|
21182
21243
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
21183
21244
|
syncOp.failed(new ChannelError("inbox", err));
|
|
@@ -21198,8 +21259,10 @@ class SyncManager {
|
|
|
21198
21259
|
const jobInfo = result.jobs[syncOp.jobId];
|
|
21199
21260
|
let completedJobInfo;
|
|
21200
21261
|
try {
|
|
21201
|
-
completedJobInfo = await this.awaiter.waitForJob(jobInfo.id);
|
|
21262
|
+
completedJobInfo = await this.awaiter.waitForJob(jobInfo.id, this.abortController.signal);
|
|
21202
21263
|
} catch (error) {
|
|
21264
|
+
if (this.isShutdown)
|
|
21265
|
+
continue;
|
|
21203
21266
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
21204
21267
|
syncOp.failed(new ChannelError("inbox", err));
|
|
21205
21268
|
remote.channel.deadLetter.add(syncOp);
|
|
@@ -21246,7 +21309,7 @@ class SyncManager {
|
|
|
21246
21309
|
remote.channel.outbox.add(...syncOps);
|
|
21247
21310
|
}
|
|
21248
21311
|
async getOperationsForRemote(remote, ackOrdinal) {
|
|
21249
|
-
const results = await this.operationIndex.find(remote.collectionId, ackOrdinal, { excludeSourceRemote: remote.name });
|
|
21312
|
+
const results = await this.operationIndex.find(remote.collectionId, ackOrdinal, { excludeSourceRemote: remote.name }, undefined, this.abortController.signal);
|
|
21250
21313
|
let operations = results.results.map((entry) => toOperationWithContext(entry));
|
|
21251
21314
|
const sinceTimestamp = remote.options.sinceTimestampUtcMs;
|
|
21252
21315
|
if (sinceTimestamp && sinceTimestamp !== "0") {
|
|
@@ -22569,7 +22632,7 @@ var __defProp2, __export2 = (target, all) => {
|
|
|
22569
22632
|
}
|
|
22570
22633
|
}, DocumentDeletedError, InvalidSignatureError, DowngradeNotSupportedError, DocumentNotFoundError, getNextIndexForScope = (document2, scope) => {
|
|
22571
22634
|
return document2.header.revision[scope] || 0;
|
|
22572
|
-
}, EventBusAggregateError, ReactorEventTypes, QueueEventTypes, ModuleNotFoundError, DuplicateModuleError, DuplicateManifestError, ManifestNotFoundError, DowngradeNotSupportedError2, MissingUpgradeTransitionError, InvalidUpgradeStepError, STRICT_ORDER_ACTION_TYPES, MAX_SKIP_THRESHOLD = 1000, documentScopeActions, DRIVE_DOCUMENT_TYPE = "powerhouse/document-drive", ProcessorManager, KyselyDocumentView, KyselyDocumentIndexer, DuplicateOperationError, RevisionMismatchError, exports_001_create_operation_table, exports_002_create_keyframe_table, exports_003_create_document_table, exports_004_create_document_relationship_table, exports_005_create_indexer_state_table, exports_006_create_document_snapshot_table, exports_007_create_slug_mapping_table, exports_008_create_view_state_table, exports_009_create_operation_index_tables, exports_010_create_sync_tables, exports_011_add_cursor_type_column, exports_012_add_source_remote_column, exports_013_create_sync_dead_letters_table, exports_014_create_processor_cursor_table, REACTOR_SCHEMA = "reactor", migrations, ChannelScheme, SyncOperationStatus, ChannelErrorSource, SyncEventTypes, MailboxAggregateError, ChannelError, SyncOperationAggregateError, DEFAULT_CONFIG, syncOpCounter = 0, getLatestAppliedOrdinal = (syncOps) => {
|
|
22635
|
+
}, EventBusAggregateError, ReactorEventTypes, QueueEventTypes, ModuleNotFoundError, DuplicateModuleError, DuplicateManifestError, ManifestNotFoundError, DowngradeNotSupportedError2, MissingUpgradeTransitionError, InvalidUpgradeStepError, STRICT_ORDER_ACTION_TYPES, MAX_SKIP_THRESHOLD = 1000, documentScopeActions, DRIVE_DOCUMENT_TYPE = "powerhouse/document-drive", ProcessorManager, KyselyDocumentView, KyselyDocumentIndexer, DuplicateOperationError, RevisionMismatchError, exports_001_create_operation_table, exports_002_create_keyframe_table, exports_003_create_document_table, exports_004_create_document_relationship_table, exports_005_create_indexer_state_table, exports_006_create_document_snapshot_table, exports_007_create_slug_mapping_table, exports_008_create_view_state_table, exports_009_create_operation_index_tables, exports_010_create_sync_tables, exports_011_add_cursor_type_column, exports_012_add_source_remote_column, exports_013_create_sync_dead_letters_table, exports_014_create_processor_cursor_table, REACTOR_SCHEMA = "reactor", migrations, ChannelScheme, SyncOperationStatus, ChannelErrorSource, SyncEventTypes, MailboxAggregateError, GraphQLRequestError, ChannelError, SyncOperationAggregateError, DEFAULT_CONFIG, syncOpCounter = 0, getLatestAppliedOrdinal = (syncOps) => {
|
|
22573
22636
|
let maxOrdinal = 0;
|
|
22574
22637
|
for (const syncOp of syncOps) {
|
|
22575
22638
|
if (syncOp.status === 2) {
|
|
@@ -27777,6 +27840,16 @@ var init_src = __esm(() => {
|
|
|
27777
27840
|
this.errors = errors2;
|
|
27778
27841
|
}
|
|
27779
27842
|
};
|
|
27843
|
+
GraphQLRequestError = class GraphQLRequestError extends Error {
|
|
27844
|
+
statusCode;
|
|
27845
|
+
category;
|
|
27846
|
+
constructor(message, category, statusCode) {
|
|
27847
|
+
super(message);
|
|
27848
|
+
this.name = "GraphQLRequestError";
|
|
27849
|
+
this.category = category;
|
|
27850
|
+
this.statusCode = statusCode;
|
|
27851
|
+
}
|
|
27852
|
+
};
|
|
27780
27853
|
ChannelError = class ChannelError extends Error {
|
|
27781
27854
|
source;
|
|
27782
27855
|
error;
|