@powerhousedao/reactor 6.0.0-dev.90 → 6.0.0-dev.92
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/index.js +127 -50
- package/dist/src/sync/channels/gql-req-channel.d.ts +9 -4
- package/dist/src/sync/channels/gql-req-channel.d.ts.map +1 -1
- package/dist/src/sync/errors.d.ts +6 -0
- package/dist/src/sync/errors.d.ts.map +1 -1
- package/dist/src/sync/sync-manager.d.ts +1 -0
- package/dist/src/sync/sync-manager.d.ts.map +1 -1
- package/package.json +6 -6
package/dist/src/index.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
|
+
var __returnValue = (v) => v;
|
|
3
|
+
function __exportSetter(name, newValue) {
|
|
4
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
5
|
+
}
|
|
2
6
|
var __export = (target, all) => {
|
|
3
7
|
for (var name in all)
|
|
4
8
|
__defProp(target, name, {
|
|
5
9
|
get: all[name],
|
|
6
10
|
enumerable: true,
|
|
7
11
|
configurable: true,
|
|
8
|
-
set: (
|
|
12
|
+
set: __exportSetter.bind(all, name)
|
|
9
13
|
});
|
|
10
14
|
};
|
|
11
15
|
|
|
@@ -72,15 +76,9 @@ function removeRelationshipAction(sourceId, targetId, relationshipType = "child"
|
|
|
72
76
|
// src/client/reactor-client.ts
|
|
73
77
|
import { addFile } from "document-drive/drive-document-model/gen/node/creators";
|
|
74
78
|
import { actions } from "document-model";
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
var
|
|
78
|
-
for (let i = 0;i < 256; ++i) {
|
|
79
|
-
byteToHex.push((i + 256).toString(16).slice(1));
|
|
80
|
-
}
|
|
81
|
-
function unsafeStringify(arr, offset = 0) {
|
|
82
|
-
return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
|
|
83
|
-
}
|
|
79
|
+
// ../../node_modules/.pnpm/uuid@11.1.0/node_modules/uuid/dist/esm-browser/native.js
|
|
80
|
+
var randomUUID = typeof crypto !== "undefined" && crypto.randomUUID && crypto.randomUUID.bind(crypto);
|
|
81
|
+
var native_default = { randomUUID };
|
|
84
82
|
|
|
85
83
|
// ../../node_modules/.pnpm/uuid@11.1.0/node_modules/uuid/dist/esm-browser/rng.js
|
|
86
84
|
var getRandomValues;
|
|
@@ -95,9 +93,14 @@ function rng() {
|
|
|
95
93
|
return getRandomValues(rnds8);
|
|
96
94
|
}
|
|
97
95
|
|
|
98
|
-
// ../../node_modules/.pnpm/uuid@11.1.0/node_modules/uuid/dist/esm-browser/
|
|
99
|
-
var
|
|
100
|
-
|
|
96
|
+
// ../../node_modules/.pnpm/uuid@11.1.0/node_modules/uuid/dist/esm-browser/stringify.js
|
|
97
|
+
var byteToHex = [];
|
|
98
|
+
for (let i = 0;i < 256; ++i) {
|
|
99
|
+
byteToHex.push((i + 256).toString(16).slice(1));
|
|
100
|
+
}
|
|
101
|
+
function unsafeStringify(arr, offset = 0) {
|
|
102
|
+
return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
|
|
103
|
+
}
|
|
101
104
|
|
|
102
105
|
// ../../node_modules/.pnpm/uuid@11.1.0/node_modules/uuid/dist/esm-browser/v4.js
|
|
103
106
|
function v4(options, buf, offset) {
|
|
@@ -16723,6 +16726,17 @@ class BufferedMailbox {
|
|
|
16723
16726
|
}
|
|
16724
16727
|
|
|
16725
16728
|
// src/sync/errors.ts
|
|
16729
|
+
class GraphQLRequestError extends Error {
|
|
16730
|
+
statusCode;
|
|
16731
|
+
category;
|
|
16732
|
+
constructor(message, category, statusCode) {
|
|
16733
|
+
super(message);
|
|
16734
|
+
this.name = "GraphQLRequestError";
|
|
16735
|
+
this.category = category;
|
|
16736
|
+
this.statusCode = statusCode;
|
|
16737
|
+
}
|
|
16738
|
+
}
|
|
16739
|
+
|
|
16726
16740
|
class PollingChannelError extends Error {
|
|
16727
16741
|
constructor(message) {
|
|
16728
16742
|
super(message);
|
|
@@ -17245,6 +17259,7 @@ class GqlRequestChannel {
|
|
|
17245
17259
|
cursorStorage;
|
|
17246
17260
|
operationIndex;
|
|
17247
17261
|
pollTimer;
|
|
17262
|
+
abortController = new AbortController;
|
|
17248
17263
|
isShutdown;
|
|
17249
17264
|
failureCount;
|
|
17250
17265
|
lastSuccessUtcMs;
|
|
@@ -17325,6 +17340,7 @@ class GqlRequestChannel {
|
|
|
17325
17340
|
});
|
|
17326
17341
|
}
|
|
17327
17342
|
shutdown() {
|
|
17343
|
+
this.abortController.abort();
|
|
17328
17344
|
this.bufferedOutbox.flush();
|
|
17329
17345
|
this.isShutdown = true;
|
|
17330
17346
|
this.pollTimer.stop();
|
|
@@ -17352,7 +17368,7 @@ class GqlRequestChannel {
|
|
|
17352
17368
|
};
|
|
17353
17369
|
}
|
|
17354
17370
|
async init() {
|
|
17355
|
-
await this.touchRemoteChannel();
|
|
17371
|
+
const { ackOrdinal } = await this.touchRemoteChannel();
|
|
17356
17372
|
const cursors = await this.cursorStorage.list(this.remoteName);
|
|
17357
17373
|
const inboxOrdinal = cursors.find((c) => c.cursorType === "inbox")?.cursorOrdinal ?? 0;
|
|
17358
17374
|
const outboxOrdinal = cursors.find((c) => c.cursorType === "outbox")?.cursorOrdinal ?? 0;
|
|
@@ -17360,6 +17376,9 @@ class GqlRequestChannel {
|
|
|
17360
17376
|
this.outbox.init(outboxOrdinal);
|
|
17361
17377
|
this.lastPersistedInboxOrdinal = inboxOrdinal;
|
|
17362
17378
|
this.lastPersistedOutboxOrdinal = outboxOrdinal;
|
|
17379
|
+
if (ackOrdinal > 0) {
|
|
17380
|
+
trimMailboxFromAckOrdinal(this.outbox, ackOrdinal);
|
|
17381
|
+
}
|
|
17363
17382
|
this.pollTimer.setDelegate(() => this.poll());
|
|
17364
17383
|
this.pollTimer.start();
|
|
17365
17384
|
this.transitionConnectionState("connected");
|
|
@@ -17429,34 +17448,57 @@ class GqlRequestChannel {
|
|
|
17429
17448
|
this.deadLetter.add(...syncOps);
|
|
17430
17449
|
}
|
|
17431
17450
|
handlePollError(error) {
|
|
17451
|
+
if (this.isShutdown)
|
|
17452
|
+
return true;
|
|
17432
17453
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
17433
17454
|
if (err.message.includes("Channel not found")) {
|
|
17434
17455
|
this.transitionConnectionState("reconnecting");
|
|
17435
17456
|
this.recoverFromChannelNotFound();
|
|
17436
17457
|
return true;
|
|
17437
17458
|
}
|
|
17459
|
+
const classification = this.classifyError(err);
|
|
17438
17460
|
this.failureCount++;
|
|
17439
17461
|
this.lastFailureUtcMs = Date.now();
|
|
17440
|
-
this.transitionConnectionState("error");
|
|
17441
17462
|
const channelError = new ChannelError("inbox" /* Inbox */, err);
|
|
17442
|
-
this.logger.error("GqlChannel poll error (@FailureCount): @Error", this.failureCount, channelError);
|
|
17463
|
+
this.logger.error("GqlChannel poll error (@FailureCount, @Classification): @Error", this.failureCount, classification, channelError);
|
|
17464
|
+
if (classification === "unrecoverable") {
|
|
17465
|
+
this.pollTimer.stop();
|
|
17466
|
+
this.transitionConnectionState("error");
|
|
17467
|
+
return true;
|
|
17468
|
+
}
|
|
17469
|
+
this.transitionConnectionState("error");
|
|
17443
17470
|
return false;
|
|
17444
17471
|
}
|
|
17445
17472
|
recoverFromChannelNotFound() {
|
|
17446
17473
|
this.logger.info("GqlChannel @ChannelId not found on remote, re-registering...", this.channelId);
|
|
17447
17474
|
this.pollTimer.stop();
|
|
17448
|
-
|
|
17449
|
-
|
|
17450
|
-
|
|
17451
|
-
this.
|
|
17452
|
-
|
|
17453
|
-
|
|
17454
|
-
|
|
17455
|
-
|
|
17456
|
-
|
|
17457
|
-
|
|
17458
|
-
|
|
17459
|
-
|
|
17475
|
+
const attemptRecovery = (attempt) => {
|
|
17476
|
+
if (this.isShutdown)
|
|
17477
|
+
return;
|
|
17478
|
+
this.touchRemoteChannel().then(({ ackOrdinal }) => {
|
|
17479
|
+
this.logger.info("GqlChannel @ChannelId re-registered successfully", this.channelId);
|
|
17480
|
+
this.failureCount = 0;
|
|
17481
|
+
if (ackOrdinal > 0) {
|
|
17482
|
+
trimMailboxFromAckOrdinal(this.outbox, ackOrdinal);
|
|
17483
|
+
}
|
|
17484
|
+
this.pollTimer.start();
|
|
17485
|
+
this.transitionConnectionState("connected");
|
|
17486
|
+
}).catch((recoveryError) => {
|
|
17487
|
+
const err = recoveryError instanceof Error ? recoveryError : new Error(String(recoveryError));
|
|
17488
|
+
const classification = this.classifyError(err);
|
|
17489
|
+
this.logger.error("GqlChannel @ChannelId recovery attempt @Attempt failed (@Classification): @Error", this.channelId, attempt, classification, recoveryError);
|
|
17490
|
+
this.failureCount++;
|
|
17491
|
+
this.lastFailureUtcMs = Date.now();
|
|
17492
|
+
if (classification === "unrecoverable") {
|
|
17493
|
+
this.transitionConnectionState("error");
|
|
17494
|
+
return;
|
|
17495
|
+
}
|
|
17496
|
+
this.transitionConnectionState("reconnecting");
|
|
17497
|
+
const delay = calculateBackoffDelay(attempt, this.config.retryBaseDelayMs, this.config.retryMaxDelayMs, Math.random());
|
|
17498
|
+
setTimeout(() => attemptRecovery(attempt + 1), delay);
|
|
17499
|
+
});
|
|
17500
|
+
};
|
|
17501
|
+
attemptRecovery(1);
|
|
17460
17502
|
}
|
|
17461
17503
|
async pollSyncEnvelopes(ackOrdinal, latestOrdinal) {
|
|
17462
17504
|
const query = `
|
|
@@ -17554,7 +17596,10 @@ class GqlRequestChannel {
|
|
|
17554
17596
|
} catch {}
|
|
17555
17597
|
const mutation = `
|
|
17556
17598
|
mutation TouchChannel($input: TouchChannelInput!) {
|
|
17557
|
-
touchChannel(input: $input)
|
|
17599
|
+
touchChannel(input: $input) {
|
|
17600
|
+
success
|
|
17601
|
+
ackOrdinal
|
|
17602
|
+
}
|
|
17558
17603
|
}
|
|
17559
17604
|
`;
|
|
17560
17605
|
const variables = {
|
|
@@ -17570,7 +17615,11 @@ class GqlRequestChannel {
|
|
|
17570
17615
|
sinceTimestampUtcMs
|
|
17571
17616
|
}
|
|
17572
17617
|
};
|
|
17573
|
-
await this.executeGraphQL(mutation, variables);
|
|
17618
|
+
const data = await this.executeGraphQL(mutation, variables);
|
|
17619
|
+
if (!data.touchChannel.success) {
|
|
17620
|
+
throw new GraphQLRequestError("touchChannel returned success=false", "graphql");
|
|
17621
|
+
}
|
|
17622
|
+
return { ackOrdinal: data.touchChannel.ackOrdinal };
|
|
17574
17623
|
}
|
|
17575
17624
|
attemptPush(syncOps) {
|
|
17576
17625
|
this.pushSyncOperations(syncOps).then(() => {
|
|
@@ -17580,8 +17629,11 @@ class GqlRequestChannel {
|
|
|
17580
17629
|
this.transitionConnectionState("connected");
|
|
17581
17630
|
}
|
|
17582
17631
|
}).catch((error) => {
|
|
17632
|
+
if (this.isShutdown)
|
|
17633
|
+
return;
|
|
17583
17634
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
17584
|
-
|
|
17635
|
+
const classification = this.classifyError(err);
|
|
17636
|
+
if (classification === "recoverable") {
|
|
17585
17637
|
this.pushFailureCount++;
|
|
17586
17638
|
this.pushBlocked = true;
|
|
17587
17639
|
this.logger.error("GqlChannel push failed (attempt @FailureCount), will retry: @Error", this.pushFailureCount, err);
|
|
@@ -17615,12 +17667,26 @@ class GqlRequestChannel {
|
|
|
17615
17667
|
this.attemptPush([...allItems]);
|
|
17616
17668
|
}, delay);
|
|
17617
17669
|
}
|
|
17618
|
-
|
|
17619
|
-
if (error
|
|
17620
|
-
return
|
|
17621
|
-
|
|
17622
|
-
|
|
17623
|
-
|
|
17670
|
+
classifyError(error) {
|
|
17671
|
+
if (!(error instanceof GraphQLRequestError)) {
|
|
17672
|
+
return "recoverable";
|
|
17673
|
+
}
|
|
17674
|
+
switch (error.category) {
|
|
17675
|
+
case "network":
|
|
17676
|
+
return "recoverable";
|
|
17677
|
+
case "http": {
|
|
17678
|
+
if (error.statusCode !== undefined && error.statusCode >= 500) {
|
|
17679
|
+
return "recoverable";
|
|
17680
|
+
}
|
|
17681
|
+
return "unrecoverable";
|
|
17682
|
+
}
|
|
17683
|
+
case "parse":
|
|
17684
|
+
return "recoverable";
|
|
17685
|
+
case "graphql":
|
|
17686
|
+
return "unrecoverable";
|
|
17687
|
+
case "missing-data":
|
|
17688
|
+
return "unrecoverable";
|
|
17689
|
+
}
|
|
17624
17690
|
}
|
|
17625
17691
|
async pushSyncOperations(syncOps) {
|
|
17626
17692
|
for (const syncOp of syncOps) {
|
|
@@ -17697,26 +17763,27 @@ class GqlRequestChannel {
|
|
|
17697
17763
|
body: JSON.stringify({
|
|
17698
17764
|
query,
|
|
17699
17765
|
variables
|
|
17700
|
-
})
|
|
17766
|
+
}),
|
|
17767
|
+
signal: this.abortController.signal
|
|
17701
17768
|
});
|
|
17702
17769
|
} catch (error) {
|
|
17703
|
-
throw new
|
|
17770
|
+
throw new GraphQLRequestError(`GraphQL request failed: ${error instanceof Error ? error.message : String(error)}`, "network");
|
|
17704
17771
|
}
|
|
17705
17772
|
if (!response.ok) {
|
|
17706
|
-
throw new
|
|
17773
|
+
throw new GraphQLRequestError(`GraphQL request failed: ${response.status} ${response.statusText}`, "http", response.status);
|
|
17707
17774
|
}
|
|
17708
17775
|
let result;
|
|
17709
17776
|
try {
|
|
17710
17777
|
result = await response.json();
|
|
17711
17778
|
} catch (error) {
|
|
17712
|
-
throw new
|
|
17779
|
+
throw new GraphQLRequestError(`Failed to parse GraphQL response: ${error instanceof Error ? error.message : String(error)}`, "parse");
|
|
17713
17780
|
}
|
|
17714
17781
|
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");
|
|
17715
17782
|
if (result.errors) {
|
|
17716
|
-
throw new
|
|
17783
|
+
throw new GraphQLRequestError(`GraphQL errors: ${JSON.stringify(result.errors, null, 2)}`, "graphql");
|
|
17717
17784
|
}
|
|
17718
17785
|
if (!result.data) {
|
|
17719
|
-
throw new
|
|
17786
|
+
throw new GraphQLRequestError("GraphQL response missing data field", "missing-data");
|
|
17720
17787
|
}
|
|
17721
17788
|
return result.data;
|
|
17722
17789
|
}
|
|
@@ -18660,6 +18727,7 @@ class SyncManager {
|
|
|
18660
18727
|
remotes;
|
|
18661
18728
|
awaiter;
|
|
18662
18729
|
syncAwaiter;
|
|
18730
|
+
abortController = new AbortController;
|
|
18663
18731
|
isShutdown;
|
|
18664
18732
|
eventUnsubscribe;
|
|
18665
18733
|
failedEventUnsubscribe;
|
|
@@ -18720,6 +18788,7 @@ class SyncManager {
|
|
|
18720
18788
|
}
|
|
18721
18789
|
shutdown() {
|
|
18722
18790
|
this.isShutdown = true;
|
|
18791
|
+
this.abortController.abort();
|
|
18723
18792
|
this.batchAggregator.clear();
|
|
18724
18793
|
if (this.eventUnsubscribe) {
|
|
18725
18794
|
this.eventUnsubscribe();
|
|
@@ -18912,6 +18981,8 @@ class SyncManager {
|
|
|
18912
18981
|
return Array.from(this.remotes.values()).filter((remote) => remote.collectionId === collectionId);
|
|
18913
18982
|
}
|
|
18914
18983
|
async processCompleteBatch(batch) {
|
|
18984
|
+
if (this.isShutdown)
|
|
18985
|
+
return;
|
|
18915
18986
|
const collectionIds = [
|
|
18916
18987
|
...new Set(Object.values(batch.collectionMemberships).flatMap((collections) => collections))
|
|
18917
18988
|
];
|
|
@@ -18955,8 +19026,10 @@ class SyncManager {
|
|
|
18955
19026
|
const operations = syncOp.operations.map((op) => op.operation);
|
|
18956
19027
|
let jobInfo;
|
|
18957
19028
|
try {
|
|
18958
|
-
jobInfo = await this.reactor.load(syncOp.documentId, syncOp.branch, operations,
|
|
19029
|
+
jobInfo = await this.reactor.load(syncOp.documentId, syncOp.branch, operations, this.abortController.signal, { sourceRemote: remote.name });
|
|
18959
19030
|
} catch (error) {
|
|
19031
|
+
if (this.isShutdown)
|
|
19032
|
+
return;
|
|
18960
19033
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
18961
19034
|
this.logger.error("Failed to load operations from inbox (@remote, @documentId, @error)", remote.name, syncOp.documentId, err.message);
|
|
18962
19035
|
const channelError = new ChannelError("inbox" /* Inbox */, err);
|
|
@@ -18967,8 +19040,10 @@ class SyncManager {
|
|
|
18967
19040
|
}
|
|
18968
19041
|
let completedJobInfo;
|
|
18969
19042
|
try {
|
|
18970
|
-
completedJobInfo = await this.awaiter.waitForJob(jobInfo.id);
|
|
19043
|
+
completedJobInfo = await this.awaiter.waitForJob(jobInfo.id, this.abortController.signal);
|
|
18971
19044
|
} catch (error) {
|
|
19045
|
+
if (this.isShutdown)
|
|
19046
|
+
return;
|
|
18972
19047
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
18973
19048
|
this.logger.error("Failed to wait for job completion (@remote, @documentId, @jobId, @error)", remote.name, syncOp.documentId, jobInfo.id, err.message);
|
|
18974
19049
|
const channelError = new ChannelError("inbox" /* Inbox */, err);
|
|
@@ -19003,10 +19078,10 @@ class SyncManager {
|
|
|
19003
19078
|
const request = { jobs };
|
|
19004
19079
|
let result;
|
|
19005
19080
|
try {
|
|
19006
|
-
result = await this.reactor.loadBatch(request,
|
|
19007
|
-
sourceRemote
|
|
19008
|
-
});
|
|
19081
|
+
result = await this.reactor.loadBatch(request, this.abortController.signal, { sourceRemote });
|
|
19009
19082
|
} catch (error) {
|
|
19083
|
+
if (this.isShutdown)
|
|
19084
|
+
return;
|
|
19010
19085
|
for (const { remote, syncOp } of items) {
|
|
19011
19086
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
19012
19087
|
syncOp.failed(new ChannelError("inbox" /* Inbox */, err));
|
|
@@ -19027,8 +19102,10 @@ class SyncManager {
|
|
|
19027
19102
|
const jobInfo = result.jobs[syncOp.jobId];
|
|
19028
19103
|
let completedJobInfo;
|
|
19029
19104
|
try {
|
|
19030
|
-
completedJobInfo = await this.awaiter.waitForJob(jobInfo.id);
|
|
19105
|
+
completedJobInfo = await this.awaiter.waitForJob(jobInfo.id, this.abortController.signal);
|
|
19031
19106
|
} catch (error) {
|
|
19107
|
+
if (this.isShutdown)
|
|
19108
|
+
continue;
|
|
19032
19109
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
19033
19110
|
syncOp.failed(new ChannelError("inbox" /* Inbox */, err));
|
|
19034
19111
|
remote.channel.deadLetter.add(syncOp);
|
|
@@ -19075,7 +19152,7 @@ class SyncManager {
|
|
|
19075
19152
|
remote.channel.outbox.add(...syncOps);
|
|
19076
19153
|
}
|
|
19077
19154
|
async getOperationsForRemote(remote, ackOrdinal) {
|
|
19078
|
-
const results = await this.operationIndex.find(remote.collectionId, ackOrdinal, { excludeSourceRemote: remote.name });
|
|
19155
|
+
const results = await this.operationIndex.find(remote.collectionId, ackOrdinal, { excludeSourceRemote: remote.name }, undefined, this.abortController.signal);
|
|
19079
19156
|
let operations = results.results.map((entry) => toOperationWithContext(entry));
|
|
19080
19157
|
const sinceTimestamp = remote.options.sinceTimestampUtcMs;
|
|
19081
19158
|
if (sinceTimestamp && sinceTimestamp !== "0") {
|
|
@@ -39,6 +39,7 @@ export declare class GqlRequestChannel implements IChannel {
|
|
|
39
39
|
private readonly cursorStorage;
|
|
40
40
|
private readonly operationIndex;
|
|
41
41
|
private readonly pollTimer;
|
|
42
|
+
private readonly abortController;
|
|
42
43
|
private isShutdown;
|
|
43
44
|
private failureCount;
|
|
44
45
|
private lastSuccessUtcMs?;
|
|
@@ -72,11 +73,13 @@ export declare class GqlRequestChannel implements IChannel {
|
|
|
72
73
|
*/
|
|
73
74
|
private handleRemoteDeadLetters;
|
|
74
75
|
/**
|
|
75
|
-
* Handles polling errors with
|
|
76
|
+
* Handles polling errors with error classification.
|
|
77
|
+
* Returns true if the error was handled (caller should not rethrow).
|
|
76
78
|
*/
|
|
77
79
|
private handlePollError;
|
|
78
80
|
/**
|
|
79
81
|
* Recovers from a "Channel not found" error by re-registering and restarting polling.
|
|
82
|
+
* Self-retries with backoff instead of restarting the poll timer on failure.
|
|
80
83
|
*/
|
|
81
84
|
private recoverFromChannelNotFound;
|
|
82
85
|
/**
|
|
@@ -85,6 +88,7 @@ export declare class GqlRequestChannel implements IChannel {
|
|
|
85
88
|
private pollSyncEnvelopes;
|
|
86
89
|
/**
|
|
87
90
|
* Registers or updates this channel on the remote server via GraphQL mutation.
|
|
91
|
+
* Returns the remote's ack ordinal so the client can trim its outbox.
|
|
88
92
|
*/
|
|
89
93
|
private touchRemoteChannel;
|
|
90
94
|
/**
|
|
@@ -99,10 +103,11 @@ export declare class GqlRequestChannel implements IChannel {
|
|
|
99
103
|
*/
|
|
100
104
|
private schedulePushRetry;
|
|
101
105
|
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
106
|
+
* Classifies an error as recoverable or unrecoverable based on its type.
|
|
107
|
+
* Recoverable errors are transient and worth retrying (network, 5xx, parse).
|
|
108
|
+
* Unrecoverable errors will not self-heal (auth, client errors, GraphQL rejections).
|
|
104
109
|
*/
|
|
105
|
-
private
|
|
110
|
+
private classifyError;
|
|
106
111
|
/**
|
|
107
112
|
* Pushes multiple sync operations to the remote via a single GraphQL mutation.
|
|
108
113
|
* Creates one SyncEnvelope per SyncOperation with key/dependsOn for batch ordering.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gql-req-channel.d.ts","sourceRoot":"","sources":["../../../../src/sync/channels/gql-req-channel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AAC5E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAGtE,OAAO,KAAK,EAAE,6BAA6B,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAChF,OAAO,EAAE,KAAK,QAAQ,EAAW,MAAM,eAAe,CAAC;AAEvD,OAAO,KAAK,EAEV,uBAAuB,EACvB,UAAU,EACV,YAAY,EAEb,MAAM,aAAa,CAAC;AAQrB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAOlD;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,+BAA+B;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,wEAAwE;IACxE,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,gEAAgE;IAChE,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IACvB,mCAAmC;IACnC,YAAY,EAAE,MAAM,CAAC;IACrB,oCAAoC;IACpC,MAAM,EAAE,YAAY,CAAC;IACrB,+DAA+D;IAC/D,gBAAgB,EAAE,MAAM,CAAC;IACzB,kEAAkE;IAClE,eAAe,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF;;GAEG;AACH,qBAAa,iBAAkB,YAAW,QAAQ;
|
|
1
|
+
{"version":3,"file":"gql-req-channel.d.ts","sourceRoot":"","sources":["../../../../src/sync/channels/gql-req-channel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AAC5E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAGtE,OAAO,KAAK,EAAE,6BAA6B,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAChF,OAAO,EAAE,KAAK,QAAQ,EAAW,MAAM,eAAe,CAAC;AAEvD,OAAO,KAAK,EAEV,uBAAuB,EACvB,UAAU,EACV,YAAY,EAEb,MAAM,aAAa,CAAC;AAQrB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAOlD;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,+BAA+B;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,wEAAwE;IACxE,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,gEAAgE;IAChE,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IACvB,mCAAmC;IACnC,YAAY,EAAE,MAAM,CAAC;IACrB,oCAAoC;IACpC,MAAM,EAAE,YAAY,CAAC;IACrB,+DAA+D;IAC/D,gBAAgB,EAAE,MAAM,CAAC;IACzB,kEAAkE;IAClE,eAAe,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF;;GAEG;AACH,qBAAa,iBAAkB,YAAW,QAAQ;IA2B9C,OAAO,CAAC,QAAQ,CAAC,MAAM;IA1BzB,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAkB;IAEjD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAqB;IACnD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAkB;IACjD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAa;IACvC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAyB;IACzD,OAAO,CAAC,UAAU,CAAU;IAC5B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,gBAAgB,CAAC,CAAS;IAClC,OAAO,CAAC,gBAAgB,CAAC,CAAS;IAClC,OAAO,CAAC,yBAAyB,CAAa;IAC9C,OAAO,CAAC,0BAA0B,CAAa;IAC/C,OAAO,CAAC,gBAAgB,CAAa;IACrC,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,eAAe,CAAiC;IACxD,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAC7B;gBAGO,MAAM,EAAE,OAAO,EAChC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,kBAAkB,EACjC,MAAM,EAAE,gBAAgB,EACxB,cAAc,EAAE,eAAe,EAC/B,SAAS,EAAE,UAAU;IAwFvB;;OAEG;IACH,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAgBzB,kBAAkB,IAAI,uBAAuB;IAW7C,uBAAuB,CAAC,QAAQ,EAAE,6BAA6B,GAAG,MAAM,IAAI;IAO5E;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAuB3B,OAAO,CAAC,yBAAyB;IAgBjC;;OAEG;YACW,IAAI;IA6DlB;;;OAGG;IACH,OAAO,CAAC,uBAAuB;IAuC/B;;;OAGG;IACH,OAAO,CAAC,eAAe;IAmCvB;;;OAGG;IACH,OAAO,CAAC,0BAA0B;IA6DlC;;OAEG;YACW,iBAAiB;IAqH/B;;;OAGG;YACW,kBAAkB;IAkDhC;;;;;OAKG;IACH,OAAO,CAAC,WAAW;IAwCnB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IA0BzB;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAuBrB;;;OAGG;YACW,kBAAkB;IA4DhC;;OAEG;YACW,sBAAsB;IAgBpC;;OAEG;YACW,cAAc;IA0F5B,IAAI,MAAM,IAAI,UAAU,CAEvB;CACF"}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import type { ChannelErrorSource } from "./types.js";
|
|
2
|
+
export type GraphQLRequestErrorCategory = "network" | "http" | "parse" | "graphql" | "missing-data";
|
|
3
|
+
export declare class GraphQLRequestError extends Error {
|
|
4
|
+
readonly statusCode: number | undefined;
|
|
5
|
+
readonly category: GraphQLRequestErrorCategory;
|
|
6
|
+
constructor(message: string, category: GraphQLRequestErrorCategory, statusCode?: number);
|
|
7
|
+
}
|
|
2
8
|
export declare class PollingChannelError extends Error {
|
|
3
9
|
constructor(message: string);
|
|
4
10
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/sync/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,YAAa,SAAQ,KAAK;IACrC,MAAM,EAAE,kBAAkB,CAAC;IAC3B,KAAK,EAAE,KAAK,CAAC;gBAED,MAAM,EAAE,kBAAkB,EAAE,KAAK,EAAE,KAAK;CAMrD"}
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/sync/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,MAAM,MAAM,2BAA2B,GACnC,SAAS,GACT,MAAM,GACN,OAAO,GACP,SAAS,GACT,cAAc,CAAC;AAEnB,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,QAAQ,EAAE,2BAA2B,CAAC;gBAG7C,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,2BAA2B,EACrC,UAAU,CAAC,EAAE,MAAM;CAOtB;AAED,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,YAAa,SAAQ,KAAK;IACrC,MAAM,EAAE,kBAAkB,CAAC;IAC3B,KAAK,EAAE,KAAK,CAAC;gBAED,MAAM,EAAE,kBAAkB,EAAE,KAAK,EAAE,KAAK;CAMrD"}
|
|
@@ -19,6 +19,7 @@ export declare class SyncManager implements ISyncManager {
|
|
|
19
19
|
private readonly remotes;
|
|
20
20
|
private readonly awaiter;
|
|
21
21
|
private readonly syncAwaiter;
|
|
22
|
+
private readonly abortController;
|
|
22
23
|
private isShutdown;
|
|
23
24
|
private eventUnsubscribe?;
|
|
24
25
|
private failedEventUnsubscribe?;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync-manager.d.ts","sourceRoot":"","sources":["../../../src/sync/sync-manager.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,KAAK,EAGV,QAAQ,EACT,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAMzD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAEnD,OAAO,EAEL,KAAK,OAAO,EACZ,KAAK,cAAc,EACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAEV,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,EACnB,MAAM,0BAA0B,CAAC;AAGlC,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAG7E,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,wBAAwB,EAC9B,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EACV,aAAa,EAGb,YAAY,EACZ,aAAa,EAGb,UAAU,EACX,MAAM,YAAY,CAAC;AAUpB,qBAAa,WAAY,YAAW,YAAY;IAC9C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAU;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAqB;IACnD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAqB;IACnD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAyB;IAC3D,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAkB;IACjD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAkB;IACjD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAW;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAY;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;IAC9C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAa;IACrC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;IAC1C,OAAO,CAAC,UAAU,CAAU;IAC5B,OAAO,CAAC,gBAAgB,CAAC,CAAa;IACtC,OAAO,CAAC,sBAAsB,CAAC,CAAa;IAC5C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAClD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAoB;IACtD,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAS;IACjD,OAAO,CAAC,QAAQ,CAAC,2BAA2B,CAChC;IAEL,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAa;gBAGhD,MAAM,EAAE,OAAO,EACf,aAAa,EAAE,kBAAkB,EACjC,aAAa,EAAE,kBAAkB,EACjC,iBAAiB,EAAE,sBAAsB,EACzC,cAAc,EAAE,eAAe,EAC/B,cAAc,EAAE,eAAe,EAC/B,OAAO,EAAE,QAAQ,EACjB,QAAQ,EAAE,SAAS,EACnB,uBAAuB,GAAE,MAAY;IAuBjC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA6D9B,QAAQ,IAAI,cAAc;
|
|
1
|
+
{"version":3,"file":"sync-manager.d.ts","sourceRoot":"","sources":["../../../src/sync/sync-manager.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@powerhousedao/reactor",
|
|
3
|
-
"version": "6.0.0-dev.
|
|
3
|
+
"version": "6.0.0-dev.92",
|
|
4
4
|
"description": "",
|
|
5
5
|
"repository": {
|
|
6
6
|
"url": "https://github.com/powerhouse-inc/powerhouse",
|
|
@@ -33,14 +33,14 @@
|
|
|
33
33
|
"tsx": "4.21.0",
|
|
34
34
|
"vitest": "3.2.4",
|
|
35
35
|
"@types/bun": "1.3.8",
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"document-
|
|
36
|
+
"@powerhousedao/shared": "6.0.0-dev.92",
|
|
37
|
+
"document-drive": "6.0.0-dev.92",
|
|
38
|
+
"document-model": "6.0.0-dev.92"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"@electric-sql/pglite": ">=0.3.15",
|
|
42
|
-
"document-model": "6.0.0-dev.
|
|
43
|
-
"document-drive": "6.0.0-dev.
|
|
42
|
+
"document-model": "6.0.0-dev.92",
|
|
43
|
+
"document-drive": "6.0.0-dev.92"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"prebuild": "pnpm run clean",
|