@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/store/index.js
CHANGED
|
@@ -4499,7 +4499,7 @@ var init_package = __esm(() => {
|
|
|
4499
4499
|
package_default = {
|
|
4500
4500
|
name: "@powerhousedao/connect",
|
|
4501
4501
|
productName: "Powerhouse-Connect",
|
|
4502
|
-
version: "6.0.0-dev.
|
|
4502
|
+
version: "6.0.0-dev.90",
|
|
4503
4503
|
description: "Powerhouse Connect",
|
|
4504
4504
|
main: "dist/index.html",
|
|
4505
4505
|
type: "module",
|
|
@@ -15957,6 +15957,7 @@ class GqlRequestChannel {
|
|
|
15957
15957
|
cursorStorage;
|
|
15958
15958
|
operationIndex;
|
|
15959
15959
|
pollTimer;
|
|
15960
|
+
abortController = new AbortController;
|
|
15960
15961
|
isShutdown;
|
|
15961
15962
|
failureCount;
|
|
15962
15963
|
lastSuccessUtcMs;
|
|
@@ -16037,6 +16038,7 @@ class GqlRequestChannel {
|
|
|
16037
16038
|
});
|
|
16038
16039
|
}
|
|
16039
16040
|
shutdown() {
|
|
16041
|
+
this.abortController.abort();
|
|
16040
16042
|
this.bufferedOutbox.flush();
|
|
16041
16043
|
this.isShutdown = true;
|
|
16042
16044
|
this.pollTimer.stop();
|
|
@@ -16064,7 +16066,7 @@ class GqlRequestChannel {
|
|
|
16064
16066
|
};
|
|
16065
16067
|
}
|
|
16066
16068
|
async init() {
|
|
16067
|
-
await this.touchRemoteChannel();
|
|
16069
|
+
const { ackOrdinal } = await this.touchRemoteChannel();
|
|
16068
16070
|
const cursors = await this.cursorStorage.list(this.remoteName);
|
|
16069
16071
|
const inboxOrdinal = cursors.find((c) => c.cursorType === "inbox")?.cursorOrdinal ?? 0;
|
|
16070
16072
|
const outboxOrdinal = cursors.find((c) => c.cursorType === "outbox")?.cursorOrdinal ?? 0;
|
|
@@ -16072,6 +16074,9 @@ class GqlRequestChannel {
|
|
|
16072
16074
|
this.outbox.init(outboxOrdinal);
|
|
16073
16075
|
this.lastPersistedInboxOrdinal = inboxOrdinal;
|
|
16074
16076
|
this.lastPersistedOutboxOrdinal = outboxOrdinal;
|
|
16077
|
+
if (ackOrdinal > 0) {
|
|
16078
|
+
trimMailboxFromAckOrdinal(this.outbox, ackOrdinal);
|
|
16079
|
+
}
|
|
16075
16080
|
this.pollTimer.setDelegate(() => this.poll());
|
|
16076
16081
|
this.pollTimer.start();
|
|
16077
16082
|
this.transitionConnectionState("connected");
|
|
@@ -16141,34 +16146,57 @@ class GqlRequestChannel {
|
|
|
16141
16146
|
this.deadLetter.add(...syncOps);
|
|
16142
16147
|
}
|
|
16143
16148
|
handlePollError(error) {
|
|
16149
|
+
if (this.isShutdown)
|
|
16150
|
+
return true;
|
|
16144
16151
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
16145
16152
|
if (err.message.includes("Channel not found")) {
|
|
16146
16153
|
this.transitionConnectionState("reconnecting");
|
|
16147
16154
|
this.recoverFromChannelNotFound();
|
|
16148
16155
|
return true;
|
|
16149
16156
|
}
|
|
16157
|
+
const classification = this.classifyError(err);
|
|
16150
16158
|
this.failureCount++;
|
|
16151
16159
|
this.lastFailureUtcMs = Date.now();
|
|
16152
|
-
this.transitionConnectionState("error");
|
|
16153
16160
|
const channelError = new ChannelError("inbox", err);
|
|
16154
|
-
this.logger.error("GqlChannel poll error (@FailureCount): @Error", this.failureCount, channelError);
|
|
16161
|
+
this.logger.error("GqlChannel poll error (@FailureCount, @Classification): @Error", this.failureCount, classification, channelError);
|
|
16162
|
+
if (classification === "unrecoverable") {
|
|
16163
|
+
this.pollTimer.stop();
|
|
16164
|
+
this.transitionConnectionState("error");
|
|
16165
|
+
return true;
|
|
16166
|
+
}
|
|
16167
|
+
this.transitionConnectionState("error");
|
|
16155
16168
|
return false;
|
|
16156
16169
|
}
|
|
16157
16170
|
recoverFromChannelNotFound() {
|
|
16158
16171
|
this.logger.info("GqlChannel @ChannelId not found on remote, re-registering...", this.channelId);
|
|
16159
16172
|
this.pollTimer.stop();
|
|
16160
|
-
|
|
16161
|
-
|
|
16162
|
-
|
|
16163
|
-
this.
|
|
16164
|
-
|
|
16165
|
-
|
|
16166
|
-
|
|
16167
|
-
|
|
16168
|
-
|
|
16169
|
-
|
|
16170
|
-
|
|
16171
|
-
|
|
16173
|
+
const attemptRecovery = (attempt) => {
|
|
16174
|
+
if (this.isShutdown)
|
|
16175
|
+
return;
|
|
16176
|
+
this.touchRemoteChannel().then(({ ackOrdinal }) => {
|
|
16177
|
+
this.logger.info("GqlChannel @ChannelId re-registered successfully", this.channelId);
|
|
16178
|
+
this.failureCount = 0;
|
|
16179
|
+
if (ackOrdinal > 0) {
|
|
16180
|
+
trimMailboxFromAckOrdinal(this.outbox, ackOrdinal);
|
|
16181
|
+
}
|
|
16182
|
+
this.pollTimer.start();
|
|
16183
|
+
this.transitionConnectionState("connected");
|
|
16184
|
+
}).catch((recoveryError) => {
|
|
16185
|
+
const err = recoveryError instanceof Error ? recoveryError : new Error(String(recoveryError));
|
|
16186
|
+
const classification = this.classifyError(err);
|
|
16187
|
+
this.logger.error("GqlChannel @ChannelId recovery attempt @Attempt failed (@Classification): @Error", this.channelId, attempt, classification, recoveryError);
|
|
16188
|
+
this.failureCount++;
|
|
16189
|
+
this.lastFailureUtcMs = Date.now();
|
|
16190
|
+
if (classification === "unrecoverable") {
|
|
16191
|
+
this.transitionConnectionState("error");
|
|
16192
|
+
return;
|
|
16193
|
+
}
|
|
16194
|
+
this.transitionConnectionState("reconnecting");
|
|
16195
|
+
const delay = calculateBackoffDelay(attempt, this.config.retryBaseDelayMs, this.config.retryMaxDelayMs, Math.random());
|
|
16196
|
+
setTimeout(() => attemptRecovery(attempt + 1), delay);
|
|
16197
|
+
});
|
|
16198
|
+
};
|
|
16199
|
+
attemptRecovery(1);
|
|
16172
16200
|
}
|
|
16173
16201
|
async pollSyncEnvelopes(ackOrdinal, latestOrdinal) {
|
|
16174
16202
|
const query = `
|
|
@@ -16266,7 +16294,10 @@ class GqlRequestChannel {
|
|
|
16266
16294
|
} catch {}
|
|
16267
16295
|
const mutation = `
|
|
16268
16296
|
mutation TouchChannel($input: TouchChannelInput!) {
|
|
16269
|
-
touchChannel(input: $input)
|
|
16297
|
+
touchChannel(input: $input) {
|
|
16298
|
+
success
|
|
16299
|
+
ackOrdinal
|
|
16300
|
+
}
|
|
16270
16301
|
}
|
|
16271
16302
|
`;
|
|
16272
16303
|
const variables = {
|
|
@@ -16282,7 +16313,11 @@ class GqlRequestChannel {
|
|
|
16282
16313
|
sinceTimestampUtcMs
|
|
16283
16314
|
}
|
|
16284
16315
|
};
|
|
16285
|
-
await this.executeGraphQL(mutation, variables);
|
|
16316
|
+
const data = await this.executeGraphQL(mutation, variables);
|
|
16317
|
+
if (!data.touchChannel.success) {
|
|
16318
|
+
throw new GraphQLRequestError("touchChannel returned success=false", "graphql");
|
|
16319
|
+
}
|
|
16320
|
+
return { ackOrdinal: data.touchChannel.ackOrdinal };
|
|
16286
16321
|
}
|
|
16287
16322
|
attemptPush(syncOps) {
|
|
16288
16323
|
this.pushSyncOperations(syncOps).then(() => {
|
|
@@ -16292,8 +16327,11 @@ class GqlRequestChannel {
|
|
|
16292
16327
|
this.transitionConnectionState("connected");
|
|
16293
16328
|
}
|
|
16294
16329
|
}).catch((error) => {
|
|
16330
|
+
if (this.isShutdown)
|
|
16331
|
+
return;
|
|
16295
16332
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
16296
|
-
|
|
16333
|
+
const classification = this.classifyError(err);
|
|
16334
|
+
if (classification === "recoverable") {
|
|
16297
16335
|
this.pushFailureCount++;
|
|
16298
16336
|
this.pushBlocked = true;
|
|
16299
16337
|
this.logger.error("GqlChannel push failed (attempt @FailureCount), will retry: @Error", this.pushFailureCount, err);
|
|
@@ -16327,12 +16365,26 @@ class GqlRequestChannel {
|
|
|
16327
16365
|
this.attemptPush([...allItems]);
|
|
16328
16366
|
}, delay);
|
|
16329
16367
|
}
|
|
16330
|
-
|
|
16331
|
-
if (error
|
|
16332
|
-
return
|
|
16333
|
-
|
|
16334
|
-
|
|
16335
|
-
|
|
16368
|
+
classifyError(error) {
|
|
16369
|
+
if (!(error instanceof GraphQLRequestError)) {
|
|
16370
|
+
return "recoverable";
|
|
16371
|
+
}
|
|
16372
|
+
switch (error.category) {
|
|
16373
|
+
case "network":
|
|
16374
|
+
return "recoverable";
|
|
16375
|
+
case "http": {
|
|
16376
|
+
if (error.statusCode !== undefined && error.statusCode >= 500) {
|
|
16377
|
+
return "recoverable";
|
|
16378
|
+
}
|
|
16379
|
+
return "unrecoverable";
|
|
16380
|
+
}
|
|
16381
|
+
case "parse":
|
|
16382
|
+
return "recoverable";
|
|
16383
|
+
case "graphql":
|
|
16384
|
+
return "unrecoverable";
|
|
16385
|
+
case "missing-data":
|
|
16386
|
+
return "unrecoverable";
|
|
16387
|
+
}
|
|
16336
16388
|
}
|
|
16337
16389
|
async pushSyncOperations(syncOps) {
|
|
16338
16390
|
for (const syncOp of syncOps) {
|
|
@@ -16409,26 +16461,27 @@ class GqlRequestChannel {
|
|
|
16409
16461
|
body: JSON.stringify({
|
|
16410
16462
|
query,
|
|
16411
16463
|
variables
|
|
16412
|
-
})
|
|
16464
|
+
}),
|
|
16465
|
+
signal: this.abortController.signal
|
|
16413
16466
|
});
|
|
16414
16467
|
} catch (error) {
|
|
16415
|
-
throw new
|
|
16468
|
+
throw new GraphQLRequestError(`GraphQL request failed: ${error instanceof Error ? error.message : String(error)}`, "network");
|
|
16416
16469
|
}
|
|
16417
16470
|
if (!response.ok) {
|
|
16418
|
-
throw new
|
|
16471
|
+
throw new GraphQLRequestError(`GraphQL request failed: ${response.status} ${response.statusText}`, "http", response.status);
|
|
16419
16472
|
}
|
|
16420
16473
|
let result;
|
|
16421
16474
|
try {
|
|
16422
16475
|
result = await response.json();
|
|
16423
16476
|
} catch (error) {
|
|
16424
|
-
throw new
|
|
16477
|
+
throw new GraphQLRequestError(`Failed to parse GraphQL response: ${error instanceof Error ? error.message : String(error)}`, "parse");
|
|
16425
16478
|
}
|
|
16426
16479
|
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");
|
|
16427
16480
|
if (result.errors) {
|
|
16428
|
-
throw new
|
|
16481
|
+
throw new GraphQLRequestError(`GraphQL errors: ${JSON.stringify(result.errors, null, 2)}`, "graphql");
|
|
16429
16482
|
}
|
|
16430
16483
|
if (!result.data) {
|
|
16431
|
-
throw new
|
|
16484
|
+
throw new GraphQLRequestError("GraphQL response missing data field", "missing-data");
|
|
16432
16485
|
}
|
|
16433
16486
|
return result.data;
|
|
16434
16487
|
}
|
|
@@ -17348,6 +17401,7 @@ class SyncManager {
|
|
|
17348
17401
|
remotes;
|
|
17349
17402
|
awaiter;
|
|
17350
17403
|
syncAwaiter;
|
|
17404
|
+
abortController = new AbortController;
|
|
17351
17405
|
isShutdown;
|
|
17352
17406
|
eventUnsubscribe;
|
|
17353
17407
|
failedEventUnsubscribe;
|
|
@@ -17408,6 +17462,7 @@ class SyncManager {
|
|
|
17408
17462
|
}
|
|
17409
17463
|
shutdown() {
|
|
17410
17464
|
this.isShutdown = true;
|
|
17465
|
+
this.abortController.abort();
|
|
17411
17466
|
this.batchAggregator.clear();
|
|
17412
17467
|
if (this.eventUnsubscribe) {
|
|
17413
17468
|
this.eventUnsubscribe();
|
|
@@ -17600,6 +17655,8 @@ class SyncManager {
|
|
|
17600
17655
|
return Array.from(this.remotes.values()).filter((remote) => remote.collectionId === collectionId);
|
|
17601
17656
|
}
|
|
17602
17657
|
async processCompleteBatch(batch) {
|
|
17658
|
+
if (this.isShutdown)
|
|
17659
|
+
return;
|
|
17603
17660
|
const collectionIds = [
|
|
17604
17661
|
...new Set(Object.values(batch.collectionMemberships).flatMap((collections) => collections))
|
|
17605
17662
|
];
|
|
@@ -17643,8 +17700,10 @@ class SyncManager {
|
|
|
17643
17700
|
const operations = syncOp.operations.map((op) => op.operation);
|
|
17644
17701
|
let jobInfo;
|
|
17645
17702
|
try {
|
|
17646
|
-
jobInfo = await this.reactor.load(syncOp.documentId, syncOp.branch, operations,
|
|
17703
|
+
jobInfo = await this.reactor.load(syncOp.documentId, syncOp.branch, operations, this.abortController.signal, { sourceRemote: remote.name });
|
|
17647
17704
|
} catch (error) {
|
|
17705
|
+
if (this.isShutdown)
|
|
17706
|
+
return;
|
|
17648
17707
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
17649
17708
|
this.logger.error("Failed to load operations from inbox (@remote, @documentId, @error)", remote.name, syncOp.documentId, err.message);
|
|
17650
17709
|
const channelError = new ChannelError("inbox", err);
|
|
@@ -17655,8 +17714,10 @@ class SyncManager {
|
|
|
17655
17714
|
}
|
|
17656
17715
|
let completedJobInfo;
|
|
17657
17716
|
try {
|
|
17658
|
-
completedJobInfo = await this.awaiter.waitForJob(jobInfo.id);
|
|
17717
|
+
completedJobInfo = await this.awaiter.waitForJob(jobInfo.id, this.abortController.signal);
|
|
17659
17718
|
} catch (error) {
|
|
17719
|
+
if (this.isShutdown)
|
|
17720
|
+
return;
|
|
17660
17721
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
17661
17722
|
this.logger.error("Failed to wait for job completion (@remote, @documentId, @jobId, @error)", remote.name, syncOp.documentId, jobInfo.id, err.message);
|
|
17662
17723
|
const channelError = new ChannelError("inbox", err);
|
|
@@ -17691,10 +17752,10 @@ class SyncManager {
|
|
|
17691
17752
|
const request = { jobs };
|
|
17692
17753
|
let result;
|
|
17693
17754
|
try {
|
|
17694
|
-
result = await this.reactor.loadBatch(request,
|
|
17695
|
-
sourceRemote
|
|
17696
|
-
});
|
|
17755
|
+
result = await this.reactor.loadBatch(request, this.abortController.signal, { sourceRemote });
|
|
17697
17756
|
} catch (error) {
|
|
17757
|
+
if (this.isShutdown)
|
|
17758
|
+
return;
|
|
17698
17759
|
for (const { remote, syncOp } of items) {
|
|
17699
17760
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
17700
17761
|
syncOp.failed(new ChannelError("inbox", err));
|
|
@@ -17715,8 +17776,10 @@ class SyncManager {
|
|
|
17715
17776
|
const jobInfo = result.jobs[syncOp.jobId];
|
|
17716
17777
|
let completedJobInfo;
|
|
17717
17778
|
try {
|
|
17718
|
-
completedJobInfo = await this.awaiter.waitForJob(jobInfo.id);
|
|
17779
|
+
completedJobInfo = await this.awaiter.waitForJob(jobInfo.id, this.abortController.signal);
|
|
17719
17780
|
} catch (error) {
|
|
17781
|
+
if (this.isShutdown)
|
|
17782
|
+
continue;
|
|
17720
17783
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
17721
17784
|
syncOp.failed(new ChannelError("inbox", err));
|
|
17722
17785
|
remote.channel.deadLetter.add(syncOp);
|
|
@@ -17763,7 +17826,7 @@ class SyncManager {
|
|
|
17763
17826
|
remote.channel.outbox.add(...syncOps);
|
|
17764
17827
|
}
|
|
17765
17828
|
async getOperationsForRemote(remote, ackOrdinal) {
|
|
17766
|
-
const results = await this.operationIndex.find(remote.collectionId, ackOrdinal, { excludeSourceRemote: remote.name });
|
|
17829
|
+
const results = await this.operationIndex.find(remote.collectionId, ackOrdinal, { excludeSourceRemote: remote.name }, undefined, this.abortController.signal);
|
|
17767
17830
|
let operations = results.results.map((entry) => toOperationWithContext(entry));
|
|
17768
17831
|
const sinceTimestamp = remote.options.sinceTimestampUtcMs;
|
|
17769
17832
|
if (sinceTimestamp && sinceTimestamp !== "0") {
|
|
@@ -19086,7 +19149,7 @@ var __defProp2, __export2 = (target, all) => {
|
|
|
19086
19149
|
}
|
|
19087
19150
|
}, DocumentDeletedError, InvalidSignatureError, DowngradeNotSupportedError, DocumentNotFoundError, getNextIndexForScope = (document2, scope) => {
|
|
19088
19151
|
return document2.header.revision[scope] || 0;
|
|
19089
|
-
}, 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) => {
|
|
19152
|
+
}, 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) => {
|
|
19090
19153
|
let maxOrdinal = 0;
|
|
19091
19154
|
for (const syncOp of syncOps) {
|
|
19092
19155
|
if (syncOp.status === 2) {
|
|
@@ -24294,6 +24357,16 @@ var init_src = __esm(() => {
|
|
|
24294
24357
|
this.errors = errors2;
|
|
24295
24358
|
}
|
|
24296
24359
|
};
|
|
24360
|
+
GraphQLRequestError = class GraphQLRequestError extends Error {
|
|
24361
|
+
statusCode;
|
|
24362
|
+
category;
|
|
24363
|
+
constructor(message, category, statusCode) {
|
|
24364
|
+
super(message);
|
|
24365
|
+
this.name = "GraphQLRequestError";
|
|
24366
|
+
this.category = category;
|
|
24367
|
+
this.statusCode = statusCode;
|
|
24368
|
+
}
|
|
24369
|
+
};
|
|
24297
24370
|
ChannelError = class ChannelError extends Error {
|
|
24298
24371
|
source;
|
|
24299
24372
|
error;
|
package/dist/src/utils/index.js
CHANGED
|
@@ -4499,7 +4499,7 @@ var init_package = __esm(() => {
|
|
|
4499
4499
|
package_default = {
|
|
4500
4500
|
name: "@powerhousedao/connect",
|
|
4501
4501
|
productName: "Powerhouse-Connect",
|
|
4502
|
-
version: "6.0.0-dev.
|
|
4502
|
+
version: "6.0.0-dev.90",
|
|
4503
4503
|
description: "Powerhouse Connect",
|
|
4504
4504
|
main: "dist/index.html",
|
|
4505
4505
|
type: "module",
|
|
@@ -15957,6 +15957,7 @@ class GqlRequestChannel {
|
|
|
15957
15957
|
cursorStorage;
|
|
15958
15958
|
operationIndex;
|
|
15959
15959
|
pollTimer;
|
|
15960
|
+
abortController = new AbortController;
|
|
15960
15961
|
isShutdown;
|
|
15961
15962
|
failureCount;
|
|
15962
15963
|
lastSuccessUtcMs;
|
|
@@ -16037,6 +16038,7 @@ class GqlRequestChannel {
|
|
|
16037
16038
|
});
|
|
16038
16039
|
}
|
|
16039
16040
|
shutdown() {
|
|
16041
|
+
this.abortController.abort();
|
|
16040
16042
|
this.bufferedOutbox.flush();
|
|
16041
16043
|
this.isShutdown = true;
|
|
16042
16044
|
this.pollTimer.stop();
|
|
@@ -16064,7 +16066,7 @@ class GqlRequestChannel {
|
|
|
16064
16066
|
};
|
|
16065
16067
|
}
|
|
16066
16068
|
async init() {
|
|
16067
|
-
await this.touchRemoteChannel();
|
|
16069
|
+
const { ackOrdinal } = await this.touchRemoteChannel();
|
|
16068
16070
|
const cursors = await this.cursorStorage.list(this.remoteName);
|
|
16069
16071
|
const inboxOrdinal = cursors.find((c) => c.cursorType === "inbox")?.cursorOrdinal ?? 0;
|
|
16070
16072
|
const outboxOrdinal = cursors.find((c) => c.cursorType === "outbox")?.cursorOrdinal ?? 0;
|
|
@@ -16072,6 +16074,9 @@ class GqlRequestChannel {
|
|
|
16072
16074
|
this.outbox.init(outboxOrdinal);
|
|
16073
16075
|
this.lastPersistedInboxOrdinal = inboxOrdinal;
|
|
16074
16076
|
this.lastPersistedOutboxOrdinal = outboxOrdinal;
|
|
16077
|
+
if (ackOrdinal > 0) {
|
|
16078
|
+
trimMailboxFromAckOrdinal(this.outbox, ackOrdinal);
|
|
16079
|
+
}
|
|
16075
16080
|
this.pollTimer.setDelegate(() => this.poll());
|
|
16076
16081
|
this.pollTimer.start();
|
|
16077
16082
|
this.transitionConnectionState("connected");
|
|
@@ -16141,34 +16146,57 @@ class GqlRequestChannel {
|
|
|
16141
16146
|
this.deadLetter.add(...syncOps);
|
|
16142
16147
|
}
|
|
16143
16148
|
handlePollError(error) {
|
|
16149
|
+
if (this.isShutdown)
|
|
16150
|
+
return true;
|
|
16144
16151
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
16145
16152
|
if (err.message.includes("Channel not found")) {
|
|
16146
16153
|
this.transitionConnectionState("reconnecting");
|
|
16147
16154
|
this.recoverFromChannelNotFound();
|
|
16148
16155
|
return true;
|
|
16149
16156
|
}
|
|
16157
|
+
const classification = this.classifyError(err);
|
|
16150
16158
|
this.failureCount++;
|
|
16151
16159
|
this.lastFailureUtcMs = Date.now();
|
|
16152
|
-
this.transitionConnectionState("error");
|
|
16153
16160
|
const channelError = new ChannelError("inbox", err);
|
|
16154
|
-
this.logger.error("GqlChannel poll error (@FailureCount): @Error", this.failureCount, channelError);
|
|
16161
|
+
this.logger.error("GqlChannel poll error (@FailureCount, @Classification): @Error", this.failureCount, classification, channelError);
|
|
16162
|
+
if (classification === "unrecoverable") {
|
|
16163
|
+
this.pollTimer.stop();
|
|
16164
|
+
this.transitionConnectionState("error");
|
|
16165
|
+
return true;
|
|
16166
|
+
}
|
|
16167
|
+
this.transitionConnectionState("error");
|
|
16155
16168
|
return false;
|
|
16156
16169
|
}
|
|
16157
16170
|
recoverFromChannelNotFound() {
|
|
16158
16171
|
this.logger.info("GqlChannel @ChannelId not found on remote, re-registering...", this.channelId);
|
|
16159
16172
|
this.pollTimer.stop();
|
|
16160
|
-
|
|
16161
|
-
|
|
16162
|
-
|
|
16163
|
-
this.
|
|
16164
|
-
|
|
16165
|
-
|
|
16166
|
-
|
|
16167
|
-
|
|
16168
|
-
|
|
16169
|
-
|
|
16170
|
-
|
|
16171
|
-
|
|
16173
|
+
const attemptRecovery = (attempt) => {
|
|
16174
|
+
if (this.isShutdown)
|
|
16175
|
+
return;
|
|
16176
|
+
this.touchRemoteChannel().then(({ ackOrdinal }) => {
|
|
16177
|
+
this.logger.info("GqlChannel @ChannelId re-registered successfully", this.channelId);
|
|
16178
|
+
this.failureCount = 0;
|
|
16179
|
+
if (ackOrdinal > 0) {
|
|
16180
|
+
trimMailboxFromAckOrdinal(this.outbox, ackOrdinal);
|
|
16181
|
+
}
|
|
16182
|
+
this.pollTimer.start();
|
|
16183
|
+
this.transitionConnectionState("connected");
|
|
16184
|
+
}).catch((recoveryError) => {
|
|
16185
|
+
const err = recoveryError instanceof Error ? recoveryError : new Error(String(recoveryError));
|
|
16186
|
+
const classification = this.classifyError(err);
|
|
16187
|
+
this.logger.error("GqlChannel @ChannelId recovery attempt @Attempt failed (@Classification): @Error", this.channelId, attempt, classification, recoveryError);
|
|
16188
|
+
this.failureCount++;
|
|
16189
|
+
this.lastFailureUtcMs = Date.now();
|
|
16190
|
+
if (classification === "unrecoverable") {
|
|
16191
|
+
this.transitionConnectionState("error");
|
|
16192
|
+
return;
|
|
16193
|
+
}
|
|
16194
|
+
this.transitionConnectionState("reconnecting");
|
|
16195
|
+
const delay = calculateBackoffDelay(attempt, this.config.retryBaseDelayMs, this.config.retryMaxDelayMs, Math.random());
|
|
16196
|
+
setTimeout(() => attemptRecovery(attempt + 1), delay);
|
|
16197
|
+
});
|
|
16198
|
+
};
|
|
16199
|
+
attemptRecovery(1);
|
|
16172
16200
|
}
|
|
16173
16201
|
async pollSyncEnvelopes(ackOrdinal, latestOrdinal) {
|
|
16174
16202
|
const query = `
|
|
@@ -16266,7 +16294,10 @@ class GqlRequestChannel {
|
|
|
16266
16294
|
} catch {}
|
|
16267
16295
|
const mutation = `
|
|
16268
16296
|
mutation TouchChannel($input: TouchChannelInput!) {
|
|
16269
|
-
touchChannel(input: $input)
|
|
16297
|
+
touchChannel(input: $input) {
|
|
16298
|
+
success
|
|
16299
|
+
ackOrdinal
|
|
16300
|
+
}
|
|
16270
16301
|
}
|
|
16271
16302
|
`;
|
|
16272
16303
|
const variables = {
|
|
@@ -16282,7 +16313,11 @@ class GqlRequestChannel {
|
|
|
16282
16313
|
sinceTimestampUtcMs
|
|
16283
16314
|
}
|
|
16284
16315
|
};
|
|
16285
|
-
await this.executeGraphQL(mutation, variables);
|
|
16316
|
+
const data = await this.executeGraphQL(mutation, variables);
|
|
16317
|
+
if (!data.touchChannel.success) {
|
|
16318
|
+
throw new GraphQLRequestError("touchChannel returned success=false", "graphql");
|
|
16319
|
+
}
|
|
16320
|
+
return { ackOrdinal: data.touchChannel.ackOrdinal };
|
|
16286
16321
|
}
|
|
16287
16322
|
attemptPush(syncOps) {
|
|
16288
16323
|
this.pushSyncOperations(syncOps).then(() => {
|
|
@@ -16292,8 +16327,11 @@ class GqlRequestChannel {
|
|
|
16292
16327
|
this.transitionConnectionState("connected");
|
|
16293
16328
|
}
|
|
16294
16329
|
}).catch((error) => {
|
|
16330
|
+
if (this.isShutdown)
|
|
16331
|
+
return;
|
|
16295
16332
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
16296
|
-
|
|
16333
|
+
const classification = this.classifyError(err);
|
|
16334
|
+
if (classification === "recoverable") {
|
|
16297
16335
|
this.pushFailureCount++;
|
|
16298
16336
|
this.pushBlocked = true;
|
|
16299
16337
|
this.logger.error("GqlChannel push failed (attempt @FailureCount), will retry: @Error", this.pushFailureCount, err);
|
|
@@ -16327,12 +16365,26 @@ class GqlRequestChannel {
|
|
|
16327
16365
|
this.attemptPush([...allItems]);
|
|
16328
16366
|
}, delay);
|
|
16329
16367
|
}
|
|
16330
|
-
|
|
16331
|
-
if (error
|
|
16332
|
-
return
|
|
16333
|
-
|
|
16334
|
-
|
|
16335
|
-
|
|
16368
|
+
classifyError(error) {
|
|
16369
|
+
if (!(error instanceof GraphQLRequestError)) {
|
|
16370
|
+
return "recoverable";
|
|
16371
|
+
}
|
|
16372
|
+
switch (error.category) {
|
|
16373
|
+
case "network":
|
|
16374
|
+
return "recoverable";
|
|
16375
|
+
case "http": {
|
|
16376
|
+
if (error.statusCode !== undefined && error.statusCode >= 500) {
|
|
16377
|
+
return "recoverable";
|
|
16378
|
+
}
|
|
16379
|
+
return "unrecoverable";
|
|
16380
|
+
}
|
|
16381
|
+
case "parse":
|
|
16382
|
+
return "recoverable";
|
|
16383
|
+
case "graphql":
|
|
16384
|
+
return "unrecoverable";
|
|
16385
|
+
case "missing-data":
|
|
16386
|
+
return "unrecoverable";
|
|
16387
|
+
}
|
|
16336
16388
|
}
|
|
16337
16389
|
async pushSyncOperations(syncOps) {
|
|
16338
16390
|
for (const syncOp of syncOps) {
|
|
@@ -16409,26 +16461,27 @@ class GqlRequestChannel {
|
|
|
16409
16461
|
body: JSON.stringify({
|
|
16410
16462
|
query,
|
|
16411
16463
|
variables
|
|
16412
|
-
})
|
|
16464
|
+
}),
|
|
16465
|
+
signal: this.abortController.signal
|
|
16413
16466
|
});
|
|
16414
16467
|
} catch (error) {
|
|
16415
|
-
throw new
|
|
16468
|
+
throw new GraphQLRequestError(`GraphQL request failed: ${error instanceof Error ? error.message : String(error)}`, "network");
|
|
16416
16469
|
}
|
|
16417
16470
|
if (!response.ok) {
|
|
16418
|
-
throw new
|
|
16471
|
+
throw new GraphQLRequestError(`GraphQL request failed: ${response.status} ${response.statusText}`, "http", response.status);
|
|
16419
16472
|
}
|
|
16420
16473
|
let result;
|
|
16421
16474
|
try {
|
|
16422
16475
|
result = await response.json();
|
|
16423
16476
|
} catch (error) {
|
|
16424
|
-
throw new
|
|
16477
|
+
throw new GraphQLRequestError(`Failed to parse GraphQL response: ${error instanceof Error ? error.message : String(error)}`, "parse");
|
|
16425
16478
|
}
|
|
16426
16479
|
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");
|
|
16427
16480
|
if (result.errors) {
|
|
16428
|
-
throw new
|
|
16481
|
+
throw new GraphQLRequestError(`GraphQL errors: ${JSON.stringify(result.errors, null, 2)}`, "graphql");
|
|
16429
16482
|
}
|
|
16430
16483
|
if (!result.data) {
|
|
16431
|
-
throw new
|
|
16484
|
+
throw new GraphQLRequestError("GraphQL response missing data field", "missing-data");
|
|
16432
16485
|
}
|
|
16433
16486
|
return result.data;
|
|
16434
16487
|
}
|
|
@@ -17348,6 +17401,7 @@ class SyncManager {
|
|
|
17348
17401
|
remotes;
|
|
17349
17402
|
awaiter;
|
|
17350
17403
|
syncAwaiter;
|
|
17404
|
+
abortController = new AbortController;
|
|
17351
17405
|
isShutdown;
|
|
17352
17406
|
eventUnsubscribe;
|
|
17353
17407
|
failedEventUnsubscribe;
|
|
@@ -17408,6 +17462,7 @@ class SyncManager {
|
|
|
17408
17462
|
}
|
|
17409
17463
|
shutdown() {
|
|
17410
17464
|
this.isShutdown = true;
|
|
17465
|
+
this.abortController.abort();
|
|
17411
17466
|
this.batchAggregator.clear();
|
|
17412
17467
|
if (this.eventUnsubscribe) {
|
|
17413
17468
|
this.eventUnsubscribe();
|
|
@@ -17600,6 +17655,8 @@ class SyncManager {
|
|
|
17600
17655
|
return Array.from(this.remotes.values()).filter((remote) => remote.collectionId === collectionId);
|
|
17601
17656
|
}
|
|
17602
17657
|
async processCompleteBatch(batch) {
|
|
17658
|
+
if (this.isShutdown)
|
|
17659
|
+
return;
|
|
17603
17660
|
const collectionIds = [
|
|
17604
17661
|
...new Set(Object.values(batch.collectionMemberships).flatMap((collections) => collections))
|
|
17605
17662
|
];
|
|
@@ -17643,8 +17700,10 @@ class SyncManager {
|
|
|
17643
17700
|
const operations = syncOp.operations.map((op) => op.operation);
|
|
17644
17701
|
let jobInfo;
|
|
17645
17702
|
try {
|
|
17646
|
-
jobInfo = await this.reactor.load(syncOp.documentId, syncOp.branch, operations,
|
|
17703
|
+
jobInfo = await this.reactor.load(syncOp.documentId, syncOp.branch, operations, this.abortController.signal, { sourceRemote: remote.name });
|
|
17647
17704
|
} catch (error) {
|
|
17705
|
+
if (this.isShutdown)
|
|
17706
|
+
return;
|
|
17648
17707
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
17649
17708
|
this.logger.error("Failed to load operations from inbox (@remote, @documentId, @error)", remote.name, syncOp.documentId, err.message);
|
|
17650
17709
|
const channelError = new ChannelError("inbox", err);
|
|
@@ -17655,8 +17714,10 @@ class SyncManager {
|
|
|
17655
17714
|
}
|
|
17656
17715
|
let completedJobInfo;
|
|
17657
17716
|
try {
|
|
17658
|
-
completedJobInfo = await this.awaiter.waitForJob(jobInfo.id);
|
|
17717
|
+
completedJobInfo = await this.awaiter.waitForJob(jobInfo.id, this.abortController.signal);
|
|
17659
17718
|
} catch (error) {
|
|
17719
|
+
if (this.isShutdown)
|
|
17720
|
+
return;
|
|
17660
17721
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
17661
17722
|
this.logger.error("Failed to wait for job completion (@remote, @documentId, @jobId, @error)", remote.name, syncOp.documentId, jobInfo.id, err.message);
|
|
17662
17723
|
const channelError = new ChannelError("inbox", err);
|
|
@@ -17691,10 +17752,10 @@ class SyncManager {
|
|
|
17691
17752
|
const request = { jobs };
|
|
17692
17753
|
let result;
|
|
17693
17754
|
try {
|
|
17694
|
-
result = await this.reactor.loadBatch(request,
|
|
17695
|
-
sourceRemote
|
|
17696
|
-
});
|
|
17755
|
+
result = await this.reactor.loadBatch(request, this.abortController.signal, { sourceRemote });
|
|
17697
17756
|
} catch (error) {
|
|
17757
|
+
if (this.isShutdown)
|
|
17758
|
+
return;
|
|
17698
17759
|
for (const { remote, syncOp } of items) {
|
|
17699
17760
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
17700
17761
|
syncOp.failed(new ChannelError("inbox", err));
|
|
@@ -17715,8 +17776,10 @@ class SyncManager {
|
|
|
17715
17776
|
const jobInfo = result.jobs[syncOp.jobId];
|
|
17716
17777
|
let completedJobInfo;
|
|
17717
17778
|
try {
|
|
17718
|
-
completedJobInfo = await this.awaiter.waitForJob(jobInfo.id);
|
|
17779
|
+
completedJobInfo = await this.awaiter.waitForJob(jobInfo.id, this.abortController.signal);
|
|
17719
17780
|
} catch (error) {
|
|
17781
|
+
if (this.isShutdown)
|
|
17782
|
+
continue;
|
|
17720
17783
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
17721
17784
|
syncOp.failed(new ChannelError("inbox", err));
|
|
17722
17785
|
remote.channel.deadLetter.add(syncOp);
|
|
@@ -17763,7 +17826,7 @@ class SyncManager {
|
|
|
17763
17826
|
remote.channel.outbox.add(...syncOps);
|
|
17764
17827
|
}
|
|
17765
17828
|
async getOperationsForRemote(remote, ackOrdinal) {
|
|
17766
|
-
const results = await this.operationIndex.find(remote.collectionId, ackOrdinal, { excludeSourceRemote: remote.name });
|
|
17829
|
+
const results = await this.operationIndex.find(remote.collectionId, ackOrdinal, { excludeSourceRemote: remote.name }, undefined, this.abortController.signal);
|
|
17767
17830
|
let operations = results.results.map((entry) => toOperationWithContext(entry));
|
|
17768
17831
|
const sinceTimestamp = remote.options.sinceTimestampUtcMs;
|
|
17769
17832
|
if (sinceTimestamp && sinceTimestamp !== "0") {
|
|
@@ -19086,7 +19149,7 @@ var __defProp2, __export2 = (target, all) => {
|
|
|
19086
19149
|
}
|
|
19087
19150
|
}, DocumentDeletedError, InvalidSignatureError, DowngradeNotSupportedError, DocumentNotFoundError, getNextIndexForScope = (document2, scope) => {
|
|
19088
19151
|
return document2.header.revision[scope] || 0;
|
|
19089
|
-
}, 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) => {
|
|
19152
|
+
}, 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) => {
|
|
19090
19153
|
let maxOrdinal = 0;
|
|
19091
19154
|
for (const syncOp of syncOps) {
|
|
19092
19155
|
if (syncOp.status === 2) {
|
|
@@ -24294,6 +24357,16 @@ var init_src = __esm(() => {
|
|
|
24294
24357
|
this.errors = errors2;
|
|
24295
24358
|
}
|
|
24296
24359
|
};
|
|
24360
|
+
GraphQLRequestError = class GraphQLRequestError extends Error {
|
|
24361
|
+
statusCode;
|
|
24362
|
+
category;
|
|
24363
|
+
constructor(message, category, statusCode) {
|
|
24364
|
+
super(message);
|
|
24365
|
+
this.name = "GraphQLRequestError";
|
|
24366
|
+
this.category = category;
|
|
24367
|
+
this.statusCode = statusCode;
|
|
24368
|
+
}
|
|
24369
|
+
};
|
|
24297
24370
|
ChannelError = class ChannelError extends Error {
|
|
24298
24371
|
source;
|
|
24299
24372
|
error;
|