@powerhousedao/reactor-api 6.0.0-dev.4 → 6.0.0-dev.41
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/codegen.js +1 -1
- package/dist/codegen.js.map +1 -1
- package/dist/src/config.d.ts +1 -2
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/config.js +1 -5
- package/dist/src/config.js.map +1 -1
- package/dist/src/graphql/auth/subgraph.js +1 -1
- package/dist/src/graphql/auth/subgraph.js.map +1 -1
- package/dist/src/graphql/base-subgraph.d.ts +2 -2
- package/dist/src/graphql/base-subgraph.d.ts.map +1 -1
- package/dist/src/graphql/base-subgraph.js.map +1 -1
- package/dist/src/graphql/document-model-subgraph.d.ts +95 -43
- package/dist/src/graphql/document-model-subgraph.d.ts.map +1 -1
- package/dist/src/graphql/document-model-subgraph.js +570 -79
- package/dist/src/graphql/document-model-subgraph.js.map +1 -1
- package/dist/src/graphql/drive-subgraph.d.ts.map +1 -1
- package/dist/src/graphql/drive-subgraph.js +1 -0
- package/dist/src/graphql/drive-subgraph.js.map +1 -1
- package/dist/src/graphql/graphql-manager.d.ts +7 -2
- package/dist/src/graphql/graphql-manager.d.ts.map +1 -1
- package/dist/src/graphql/graphql-manager.js +128 -42
- package/dist/src/graphql/graphql-manager.js.map +1 -1
- package/dist/src/graphql/index.d.ts +1 -0
- package/dist/src/graphql/index.d.ts.map +1 -1
- package/dist/src/graphql/index.js +1 -0
- package/dist/src/graphql/index.js.map +1 -1
- package/dist/src/graphql/reactor/adapters.d.ts +10 -2
- package/dist/src/graphql/reactor/adapters.d.ts.map +1 -1
- package/dist/src/graphql/reactor/adapters.js +35 -1
- package/dist/src/graphql/reactor/adapters.js.map +1 -1
- package/dist/src/graphql/reactor/factory.d.ts +1 -1
- package/dist/src/graphql/reactor/factory.d.ts.map +1 -1
- package/dist/src/graphql/reactor/factory.js +1 -1
- package/dist/src/graphql/reactor/factory.js.map +1 -1
- package/dist/src/graphql/reactor/gen/graphql.d.ts +84 -24
- package/dist/src/graphql/reactor/gen/graphql.d.ts.map +1 -1
- package/dist/src/graphql/reactor/gen/graphql.js +33 -9
- package/dist/src/graphql/reactor/gen/graphql.js.map +1 -1
- package/dist/src/graphql/reactor/index.d.ts +1 -1
- package/dist/src/graphql/reactor/index.d.ts.map +1 -1
- package/dist/src/graphql/reactor/index.js +1 -1
- package/dist/src/graphql/reactor/index.js.map +1 -1
- package/dist/src/graphql/reactor/requester.with-zod.d.ts.map +1 -1
- package/dist/src/graphql/reactor/requester.with-zod.js +100 -38
- package/dist/src/graphql/reactor/requester.with-zod.js.map +1 -1
- package/dist/src/graphql/reactor/resolvers.d.ts +41 -21
- package/dist/src/graphql/reactor/resolvers.d.ts.map +1 -1
- package/dist/src/graphql/reactor/resolvers.js +118 -64
- package/dist/src/graphql/reactor/resolvers.js.map +1 -1
- package/dist/src/graphql/reactor/schema.graphql +51 -31
- package/dist/src/graphql/reactor/subgraph.d.ts.map +1 -1
- package/dist/src/graphql/reactor/subgraph.js +91 -108
- package/dist/src/graphql/reactor/subgraph.js.map +1 -1
- package/dist/src/graphql/reactor/validation.d.ts +24 -0
- package/dist/src/graphql/reactor/validation.d.ts.map +1 -1
- package/dist/src/graphql/reactor/validation.js +15 -0
- package/dist/src/graphql/reactor/validation.js.map +1 -1
- package/dist/src/graphql/system/index.d.ts +0 -1
- package/dist/src/graphql/system/index.d.ts.map +1 -1
- package/dist/src/graphql/system/index.js +0 -1
- package/dist/src/graphql/system/index.js.map +1 -1
- package/dist/src/graphql/types.d.ts +3 -3
- package/dist/src/graphql/types.d.ts.map +1 -1
- package/dist/src/graphql/utils.d.ts.map +1 -1
- package/dist/src/graphql/utils.js +7 -3
- package/dist/src/graphql/utils.js.map +1 -1
- package/dist/src/packages/import-loader.d.ts +5 -3
- package/dist/src/packages/import-loader.d.ts.map +1 -1
- package/dist/src/packages/import-loader.js +19 -10
- package/dist/src/packages/import-loader.js.map +1 -1
- package/dist/src/packages/package-manager.d.ts +2 -2
- package/dist/src/packages/package-manager.d.ts.map +1 -1
- package/dist/src/packages/package-manager.js.map +1 -1
- package/dist/src/packages/types.d.ts +8 -4
- package/dist/src/packages/types.d.ts.map +1 -1
- package/dist/src/packages/util.d.ts +3 -2
- package/dist/src/packages/util.d.ts.map +1 -1
- package/dist/src/packages/util.js +1 -1
- package/dist/src/packages/util.js.map +1 -1
- package/dist/src/packages/vite-loader.d.ts +7 -6
- package/dist/src/packages/vite-loader.d.ts.map +1 -1
- package/dist/src/packages/vite-loader.js +21 -8
- package/dist/src/packages/vite-loader.js.map +1 -1
- package/dist/src/server.d.ts +16 -7
- package/dist/src/server.d.ts.map +1 -1
- package/dist/src/server.js +117 -59
- package/dist/src/server.js.map +1 -1
- package/dist/src/services/auth.service.d.ts.map +1 -1
- package/dist/src/services/auth.service.js +11 -20
- package/dist/src/services/auth.service.js.map +1 -1
- package/dist/src/tracing.js +1 -1
- package/dist/src/tracing.js.map +1 -1
- package/dist/src/types.d.ts +5 -5
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils/create-schema.d.ts +21 -1
- package/dist/src/utils/create-schema.d.ts.map +1 -1
- package/dist/src/utils/create-schema.js +290 -16
- package/dist/src/utils/create-schema.js.map +1 -1
- package/dist/src/utils/drive-url.d.ts +2 -0
- package/dist/src/utils/drive-url.d.ts.map +1 -0
- package/dist/src/utils/drive-url.js +3 -0
- package/dist/src/utils/drive-url.js.map +1 -0
- package/dist/src/utils/index.d.ts +1 -0
- package/dist/src/utils/index.d.ts.map +1 -1
- package/dist/src/utils/index.js +1 -0
- package/dist/src/utils/index.js.map +1 -1
- package/dist/test/document-drive-subgraph.test.d.ts +2 -0
- package/dist/test/document-drive-subgraph.test.d.ts.map +1 -0
- package/dist/test/document-drive-subgraph.test.js +186 -0
- package/dist/test/document-drive-subgraph.test.js.map +1 -0
- package/dist/test/document-model-subgraph-legacy-permissions.test.d.ts +2 -0
- package/dist/test/document-model-subgraph-legacy-permissions.test.d.ts.map +1 -0
- package/dist/test/document-model-subgraph-legacy-permissions.test.js +518 -0
- package/dist/test/document-model-subgraph-legacy-permissions.test.js.map +1 -0
- package/dist/test/document-model-subgraph-permissions.test.d.ts +2 -0
- package/dist/test/document-model-subgraph-permissions.test.d.ts.map +1 -0
- package/dist/test/document-model-subgraph-permissions.test.js +635 -0
- package/dist/test/document-model-subgraph-permissions.test.js.map +1 -0
- package/dist/test/document-model-subgraph.test.d.ts +2 -0
- package/dist/test/document-model-subgraph.test.d.ts.map +1 -0
- package/dist/test/document-model-subgraph.test.js +441 -0
- package/dist/test/document-model-subgraph.test.js.map +1 -0
- package/dist/test/permissions-integration.test.js +4 -4
- package/dist/test/permissions-integration.test.js.map +1 -1
- package/dist/test/reactor-client.test.js +4 -4
- package/dist/test/reactor-client.test.js.map +1 -1
- package/dist/test/reactor-resolvers.test.js +4 -8
- package/dist/test/reactor-resolvers.test.js.map +1 -1
- package/dist/test/reactor-subgraph-permissions.test.js +4 -7
- package/dist/test/reactor-subgraph-permissions.test.js.map +1 -1
- package/dist/test/subscriptions.test.js +2 -0
- package/dist/test/subscriptions.test.js.map +1 -1
- package/dist/test/two-reactor-gql-catchup-duplicate.test.d.ts +2 -0
- package/dist/test/two-reactor-gql-catchup-duplicate.test.d.ts.map +1 -0
- package/dist/test/two-reactor-gql-catchup-duplicate.test.js +264 -0
- package/dist/test/two-reactor-gql-catchup-duplicate.test.js.map +1 -0
- package/dist/test/two-reactor-gql-sync.test.js +86 -99
- package/dist/test/two-reactor-gql-sync.test.js.map +1 -1
- package/dist/test/utils/gql-resolver-bridge.d.ts.map +1 -1
- package/dist/test/utils/gql-resolver-bridge.js +4 -4
- package/dist/test/utils/gql-resolver-bridge.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +35 -25
- package/dist/src/graphql/system/system-subgraph.d.ts +0 -49
- package/dist/src/graphql/system/system-subgraph.d.ts.map +0 -1
- package/dist/src/graphql/system/system-subgraph.js +0 -130
- package/dist/src/graphql/system/system-subgraph.js.map +0 -1
- package/dist/test/system.test.d.ts +0 -2
- package/dist/test/system.test.d.ts.map +0 -1
- package/dist/test/system.test.js +0 -211
- package/dist/test/system.test.js.map +0 -1
- package/dist/test/three-reactor-gql-sync.test.d.ts +0 -2
- package/dist/test/three-reactor-gql-sync.test.d.ts.map +0 -1
- package/dist/test/three-reactor-gql-sync.test.js +0 -368
- package/dist/test/three-reactor-gql-sync.test.js.map +0 -1
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { CompositeChannelFactory, ConsoleLogger, driveCollectionId, JobStatus, ReactorEventTypes, ReactorBuilder, SyncBuilder, } from "@powerhousedao/reactor";
|
|
2
|
+
import { driveDocumentModelModule } from "document-drive";
|
|
3
|
+
import { documentModelDocumentModelModule, } from "document-model";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
5
|
+
import { createResolverBridge } from "./utils/gql-resolver-bridge.js";
|
|
6
|
+
async function waitForJobCompletion(reactor, jobId, timeoutMs = 5000) {
|
|
7
|
+
const startTime = Date.now();
|
|
8
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
9
|
+
const status = await reactor.getJobStatus(jobId);
|
|
10
|
+
if (status.status === JobStatus.READ_READY) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
if (status.status === JobStatus.FAILED) {
|
|
14
|
+
throw new Error(`Job failed: ${status.error?.message || "Unknown"}`);
|
|
15
|
+
}
|
|
16
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
17
|
+
}
|
|
18
|
+
throw new Error(`Job did not complete within ${timeoutMs}ms`);
|
|
19
|
+
}
|
|
20
|
+
async function waitForOperationsReady(eventBus, documentId, timeoutMs = 5000) {
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
const timeout = setTimeout(() => {
|
|
23
|
+
unsubscribe();
|
|
24
|
+
reject(new Error(`OPERATIONS_READY event for document ${documentId} not received within ${timeoutMs}ms`));
|
|
25
|
+
}, timeoutMs);
|
|
26
|
+
const unsubscribe = eventBus.subscribe(ReactorEventTypes.JOB_READ_READY, (type, event) => {
|
|
27
|
+
const hasDocument = event.operations.some((op) => op.context.documentId === documentId);
|
|
28
|
+
if (hasDocument) {
|
|
29
|
+
clearTimeout(timeout);
|
|
30
|
+
unsubscribe();
|
|
31
|
+
resolve();
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Creates a unique key for an operation using id + index + skip.
|
|
38
|
+
* Per deriveOperationId, the same operation ID can appear with different index values.
|
|
39
|
+
* A true duplicate is identified by the combination of id + index + skip.
|
|
40
|
+
*/
|
|
41
|
+
function createOperationKey(op) {
|
|
42
|
+
return `${op.operation.id}:${op.operation.index}:${op.operation.skip}`;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Creates a collector that subscribes to OPERATIONS_READY events and tracks
|
|
46
|
+
* operations for a specific document, detecting duplicates.
|
|
47
|
+
*/
|
|
48
|
+
function collectOperationsReady(eventBus, documentId, timeoutMs = 10000) {
|
|
49
|
+
const operationKeys = new Set();
|
|
50
|
+
const allOperations = [];
|
|
51
|
+
const duplicates = [];
|
|
52
|
+
let waitResolve = null;
|
|
53
|
+
let waitCount = 0;
|
|
54
|
+
const unsubscribe = eventBus.subscribe(ReactorEventTypes.JOB_READ_READY, (type, event) => {
|
|
55
|
+
for (const op of event.operations) {
|
|
56
|
+
if (op.context.documentId !== documentId) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const key = createOperationKey(op);
|
|
60
|
+
allOperations.push(op);
|
|
61
|
+
if (operationKeys.has(key)) {
|
|
62
|
+
duplicates.push(op);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
operationKeys.add(key);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (waitResolve && allOperations.length >= waitCount) {
|
|
69
|
+
waitResolve();
|
|
70
|
+
waitResolve = null;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
return {
|
|
74
|
+
operationKeys,
|
|
75
|
+
allOperations,
|
|
76
|
+
duplicates,
|
|
77
|
+
waitForCount: (count) => {
|
|
78
|
+
if (allOperations.length >= count) {
|
|
79
|
+
return Promise.resolve();
|
|
80
|
+
}
|
|
81
|
+
waitCount = count;
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
waitResolve = resolve;
|
|
84
|
+
setTimeout(() => {
|
|
85
|
+
if (waitResolve) {
|
|
86
|
+
waitResolve = null;
|
|
87
|
+
reject(new Error(`Expected ${count} operations but received ${allOperations.length} within ${timeoutMs}ms`));
|
|
88
|
+
}
|
|
89
|
+
}, timeoutMs);
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
stop: () => {
|
|
93
|
+
unsubscribe();
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Creates a collector that subscribes to JOB_FAILED events and tracks failures
|
|
99
|
+
* that indicate duplicate operations (document already exists errors).
|
|
100
|
+
*/
|
|
101
|
+
function collectDuplicateFailures(eventBus) {
|
|
102
|
+
const failures = [];
|
|
103
|
+
const unsubscribe = eventBus.subscribe(ReactorEventTypes.JOB_FAILED, (type, event) => {
|
|
104
|
+
if (event.error.message.includes("already exists")) {
|
|
105
|
+
failures.push(event);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
return {
|
|
109
|
+
failures,
|
|
110
|
+
stop: () => {
|
|
111
|
+
unsubscribe();
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
async function setupTwoReactors() {
|
|
116
|
+
const syncManagerRegistry = new Map();
|
|
117
|
+
const resolverBridge = createResolverBridge(syncManagerRegistry);
|
|
118
|
+
const logger = new ConsoleLogger(["test"]);
|
|
119
|
+
const channelFactoryA = new CompositeChannelFactory(logger);
|
|
120
|
+
const channelFactoryB = new CompositeChannelFactory(logger);
|
|
121
|
+
const models = [
|
|
122
|
+
driveDocumentModelModule,
|
|
123
|
+
documentModelDocumentModelModule,
|
|
124
|
+
];
|
|
125
|
+
const reactorAModule = await new ReactorBuilder()
|
|
126
|
+
.withDocumentModels(models)
|
|
127
|
+
.withSync(new SyncBuilder().withChannelFactory(channelFactoryA))
|
|
128
|
+
.buildModule();
|
|
129
|
+
const reactorA = reactorAModule.reactor;
|
|
130
|
+
const eventBusA = reactorAModule.eventBus;
|
|
131
|
+
const syncManagerA = reactorAModule.syncModule.syncManager;
|
|
132
|
+
const reactorBModule = await new ReactorBuilder()
|
|
133
|
+
.withDocumentModels(models)
|
|
134
|
+
.withSync(new SyncBuilder().withChannelFactory(channelFactoryB))
|
|
135
|
+
.buildModule();
|
|
136
|
+
const reactorB = reactorBModule.reactor;
|
|
137
|
+
const eventBusB = reactorBModule.eventBus;
|
|
138
|
+
const syncManagerB = reactorBModule.syncModule.syncManager;
|
|
139
|
+
syncManagerRegistry.set("reactora", syncManagerA);
|
|
140
|
+
syncManagerRegistry.set("reactorb", syncManagerB);
|
|
141
|
+
return {
|
|
142
|
+
reactorA,
|
|
143
|
+
reactorB,
|
|
144
|
+
eventBusA,
|
|
145
|
+
eventBusB,
|
|
146
|
+
syncManagerA,
|
|
147
|
+
syncManagerB,
|
|
148
|
+
resolverBridge,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
describe("Two-Reactor GQL Catchup Duplicate Operations Bug", () => {
|
|
152
|
+
let reactorA;
|
|
153
|
+
let reactorB;
|
|
154
|
+
let eventBusA;
|
|
155
|
+
let eventBusB;
|
|
156
|
+
let syncManagerA;
|
|
157
|
+
let resolverBridge;
|
|
158
|
+
beforeEach(async () => {
|
|
159
|
+
const setup = await setupTwoReactors();
|
|
160
|
+
reactorA = setup.reactorA;
|
|
161
|
+
reactorB = setup.reactorB;
|
|
162
|
+
eventBusA = setup.eventBusA;
|
|
163
|
+
eventBusB = setup.eventBusB;
|
|
164
|
+
syncManagerA = setup.syncManagerA;
|
|
165
|
+
resolverBridge = setup.resolverBridge;
|
|
166
|
+
});
|
|
167
|
+
afterEach(() => {
|
|
168
|
+
reactorA.kill();
|
|
169
|
+
reactorB.kill();
|
|
170
|
+
});
|
|
171
|
+
it("should not produce duplicate operations when remote is removed and re-added", async () => {
|
|
172
|
+
const driveDocument = driveDocumentModelModule.utils.createDocument();
|
|
173
|
+
const collectionId = driveCollectionId("main", driveDocument.header.id);
|
|
174
|
+
const jobInfo = await reactorA.create(driveDocument);
|
|
175
|
+
await waitForJobCompletion(reactorA, jobInfo.id);
|
|
176
|
+
const gqlParamsToB = {
|
|
177
|
+
url: "http://reactorB/graphql",
|
|
178
|
+
pollIntervalMs: 100,
|
|
179
|
+
maxFailures: 10,
|
|
180
|
+
retryBaseDelayMs: 50,
|
|
181
|
+
fetchFn: resolverBridge,
|
|
182
|
+
};
|
|
183
|
+
const filter = {
|
|
184
|
+
documentId: [],
|
|
185
|
+
scope: [],
|
|
186
|
+
branch: "main",
|
|
187
|
+
};
|
|
188
|
+
const readyPromiseB = waitForOperationsReady(eventBusB, driveDocument.header.id);
|
|
189
|
+
await syncManagerA.add("remoteB", collectionId, { type: "gql", parameters: gqlParamsToB }, filter);
|
|
190
|
+
await readyPromiseB;
|
|
191
|
+
const resultA = await reactorA.getOperations(driveDocument.header.id, {
|
|
192
|
+
branch: "main",
|
|
193
|
+
});
|
|
194
|
+
const opsA = Object.values(resultA).flatMap((scope) => scope.results);
|
|
195
|
+
expect(opsA.length).toBeGreaterThan(0);
|
|
196
|
+
const resultB = await reactorB.getOperations(driveDocument.header.id, {
|
|
197
|
+
branch: "main",
|
|
198
|
+
});
|
|
199
|
+
const opsB = Object.values(resultB).flatMap((scope) => scope.results);
|
|
200
|
+
expect(opsB.length).toBe(opsA.length);
|
|
201
|
+
const operationCollector = collectOperationsReady(eventBusA, driveDocument.header.id, 15000);
|
|
202
|
+
const failureCollector = collectDuplicateFailures(eventBusA);
|
|
203
|
+
try {
|
|
204
|
+
await syncManagerA.remove("remoteB");
|
|
205
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
206
|
+
await syncManagerA.add("remoteB", collectionId, { type: "gql", parameters: gqlParamsToB }, filter);
|
|
207
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
208
|
+
expect(operationCollector.duplicates.length, "Duplicate operations were received via OPERATIONS_READY events").toBe(0);
|
|
209
|
+
expect(failureCollector.failures.length, "Duplicate operations caused 'document already exists' failures - this indicates the catchup backfill bug").toBe(0);
|
|
210
|
+
}
|
|
211
|
+
finally {
|
|
212
|
+
operationCollector.stop();
|
|
213
|
+
failureCollector.stop();
|
|
214
|
+
}
|
|
215
|
+
}, 20000);
|
|
216
|
+
it("should not produce duplicate operations after multiple remove/re-add cycles", async () => {
|
|
217
|
+
const driveDocument = driveDocumentModelModule.utils.createDocument();
|
|
218
|
+
const collectionId = driveCollectionId("main", driveDocument.header.id);
|
|
219
|
+
const jobInfo = await reactorA.create(driveDocument);
|
|
220
|
+
await waitForJobCompletion(reactorA, jobInfo.id);
|
|
221
|
+
await reactorA.execute(driveDocument.header.id, "main", [
|
|
222
|
+
driveDocumentModelModule.actions.setDriveName({ name: "Test Drive" }),
|
|
223
|
+
driveDocumentModelModule.actions.addFolder({
|
|
224
|
+
id: "folder-1",
|
|
225
|
+
name: "Folder 1",
|
|
226
|
+
parentFolder: null,
|
|
227
|
+
}),
|
|
228
|
+
]);
|
|
229
|
+
const gqlParamsToB = {
|
|
230
|
+
url: "http://reactorB/graphql",
|
|
231
|
+
pollIntervalMs: 100,
|
|
232
|
+
maxFailures: 10,
|
|
233
|
+
retryBaseDelayMs: 50,
|
|
234
|
+
fetchFn: resolverBridge,
|
|
235
|
+
};
|
|
236
|
+
const filter = {
|
|
237
|
+
documentId: [],
|
|
238
|
+
scope: [],
|
|
239
|
+
branch: "main",
|
|
240
|
+
};
|
|
241
|
+
const readyPromiseB = waitForOperationsReady(eventBusB, driveDocument.header.id);
|
|
242
|
+
await syncManagerA.add("remoteB", collectionId, { type: "gql", parameters: gqlParamsToB }, filter);
|
|
243
|
+
await readyPromiseB;
|
|
244
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
245
|
+
const operationCollector = collectOperationsReady(eventBusA, driveDocument.header.id, 30000);
|
|
246
|
+
const failureCollector = collectDuplicateFailures(eventBusA);
|
|
247
|
+
try {
|
|
248
|
+
for (let cycle = 0; cycle < 3; cycle++) {
|
|
249
|
+
await syncManagerA.remove("remoteB");
|
|
250
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
251
|
+
await syncManagerA.add("remoteB", collectionId, { type: "gql", parameters: gqlParamsToB }, filter);
|
|
252
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
253
|
+
}
|
|
254
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
255
|
+
expect(operationCollector.duplicates.length, "Duplicate operations were received via OPERATIONS_READY events").toBe(0);
|
|
256
|
+
expect(failureCollector.failures.length, "Duplicate operations caused 'document already exists' failures after 3 remove/re-add cycles - this indicates the catchup backfill bug").toBe(0);
|
|
257
|
+
}
|
|
258
|
+
finally {
|
|
259
|
+
operationCollector.stop();
|
|
260
|
+
failureCollector.stop();
|
|
261
|
+
}
|
|
262
|
+
}, 45000);
|
|
263
|
+
});
|
|
264
|
+
//# sourceMappingURL=two-reactor-gql-catchup-duplicate.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"two-reactor-gql-catchup-duplicate.test.js","sourceRoot":"","sources":["../../test/two-reactor-gql-catchup-duplicate.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CompositeChannelFactory, ConsoleLogger,
|
|
1
|
+
import { CompositeChannelFactory, ConsoleLogger, driveCollectionId, JobStatus, ReactorBuilder, ReactorEventTypes, SyncBuilder, } from "@powerhousedao/reactor";
|
|
2
2
|
import { driveDocumentModelModule } from "document-drive";
|
|
3
3
|
import { documentModelDocumentModelModule, } from "document-model";
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
@@ -7,7 +7,7 @@ async function waitForJobCompletion(reactor, jobId, timeoutMs = 5000) {
|
|
|
7
7
|
const startTime = Date.now();
|
|
8
8
|
while (Date.now() - startTime < timeoutMs) {
|
|
9
9
|
const status = await reactor.getJobStatus(jobId);
|
|
10
|
-
if (status.status === JobStatus.
|
|
10
|
+
if (status.status === JobStatus.READ_READY) {
|
|
11
11
|
return;
|
|
12
12
|
}
|
|
13
13
|
if (status.status === JobStatus.FAILED) {
|
|
@@ -23,7 +23,7 @@ async function waitForOperationsReady(eventBus, documentId, timeoutMs = 5000) {
|
|
|
23
23
|
unsubscribe();
|
|
24
24
|
reject(new Error(`OPERATIONS_READY event for document ${documentId} not received within ${timeoutMs}ms`));
|
|
25
25
|
}, timeoutMs);
|
|
26
|
-
const unsubscribe = eventBus.subscribe(
|
|
26
|
+
const unsubscribe = eventBus.subscribe(ReactorEventTypes.JOB_READ_READY, (type, event) => {
|
|
27
27
|
const hasDocument = event.operations.some((op) => op.context.documentId === documentId);
|
|
28
28
|
if (hasDocument) {
|
|
29
29
|
clearTimeout(timeout);
|
|
@@ -33,24 +33,7 @@ async function waitForOperationsReady(eventBus, documentId, timeoutMs = 5000) {
|
|
|
33
33
|
});
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
|
-
async function
|
|
37
|
-
return new Promise((resolve, reject) => {
|
|
38
|
-
let count = 0;
|
|
39
|
-
const timeout = setTimeout(() => {
|
|
40
|
-
unsubscribe();
|
|
41
|
-
reject(new Error(`Expected ${expectedCount} OPERATIONS_READY events but received ${count} within ${timeoutMs}ms`));
|
|
42
|
-
}, timeoutMs);
|
|
43
|
-
const unsubscribe = eventBus.subscribe(OperationEventTypes.OPERATIONS_READY, () => {
|
|
44
|
-
count++;
|
|
45
|
-
if (count >= expectedCount) {
|
|
46
|
-
clearTimeout(timeout);
|
|
47
|
-
unsubscribe();
|
|
48
|
-
resolve();
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
async function setupTwoReactorsWithGqlChannel() {
|
|
36
|
+
async function setupTwoReactors() {
|
|
54
37
|
const syncManagerRegistry = new Map();
|
|
55
38
|
const resolverBridge = createResolverBridge(syncManagerRegistry);
|
|
56
39
|
const logger = new ConsoleLogger(["test"]);
|
|
@@ -76,6 +59,18 @@ async function setupTwoReactorsWithGqlChannel() {
|
|
|
76
59
|
const syncManagerB = reactorBModule.syncModule.syncManager;
|
|
77
60
|
syncManagerRegistry.set("reactora", syncManagerA);
|
|
78
61
|
syncManagerRegistry.set("reactorb", syncManagerB);
|
|
62
|
+
return {
|
|
63
|
+
reactorA,
|
|
64
|
+
reactorB,
|
|
65
|
+
eventBusA,
|
|
66
|
+
eventBusB,
|
|
67
|
+
syncManagerA,
|
|
68
|
+
syncManagerB,
|
|
69
|
+
resolverBridge,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
async function setupSyncForDrive(syncManagerA, driveId, resolverBridge) {
|
|
73
|
+
const collectionId = driveCollectionId("main", driveId);
|
|
79
74
|
const filter = {
|
|
80
75
|
documentId: [],
|
|
81
76
|
scope: [],
|
|
@@ -88,22 +83,23 @@ async function setupTwoReactorsWithGqlChannel() {
|
|
|
88
83
|
retryBaseDelayMs: 50,
|
|
89
84
|
fetchFn: resolverBridge,
|
|
90
85
|
};
|
|
91
|
-
|
|
92
|
-
// touchChannel automatically creates receiving channel on B
|
|
93
|
-
await syncManagerA.add("remoteB", "collection1", { type: "gql", parameters: gqlParamsToB }, filter);
|
|
94
|
-
return { reactorA, reactorB, eventBusA, eventBusB };
|
|
86
|
+
await syncManagerA.add(`remoteB-${driveId}`, collectionId, { type: "gql", parameters: gqlParamsToB }, filter);
|
|
95
87
|
}
|
|
96
88
|
describe("Two-Reactor Sync with GqlChannel", () => {
|
|
97
89
|
let reactorA;
|
|
98
90
|
let reactorB;
|
|
99
91
|
let eventBusA;
|
|
100
92
|
let eventBusB;
|
|
93
|
+
let syncManagerA;
|
|
94
|
+
let resolverBridge;
|
|
101
95
|
beforeEach(async () => {
|
|
102
|
-
const setup = await
|
|
96
|
+
const setup = await setupTwoReactors();
|
|
103
97
|
reactorA = setup.reactorA;
|
|
104
98
|
reactorB = setup.reactorB;
|
|
105
99
|
eventBusA = setup.eventBusA;
|
|
106
100
|
eventBusB = setup.eventBusB;
|
|
101
|
+
syncManagerA = setup.syncManagerA;
|
|
102
|
+
resolverBridge = setup.resolverBridge;
|
|
107
103
|
});
|
|
108
104
|
afterEach(() => {
|
|
109
105
|
reactorA.kill();
|
|
@@ -111,6 +107,7 @@ describe("Two-Reactor Sync with GqlChannel", () => {
|
|
|
111
107
|
});
|
|
112
108
|
it("should sync operation from ReactorA to ReactorB via GqlChannel", async () => {
|
|
113
109
|
const document = driveDocumentModelModule.utils.createDocument();
|
|
110
|
+
await setupSyncForDrive(syncManagerA, document.header.id, resolverBridge);
|
|
114
111
|
const readyPromise = waitForOperationsReady(eventBusB, document.header.id);
|
|
115
112
|
const jobInfo = await reactorA.create(document);
|
|
116
113
|
await waitForJobCompletion(reactorA, jobInfo.id);
|
|
@@ -130,10 +127,11 @@ describe("Two-Reactor Sync with GqlChannel", () => {
|
|
|
130
127
|
}
|
|
131
128
|
const docA = await reactorA.get(document.header.id, { branch: "main" });
|
|
132
129
|
const docB = await reactorB.get(document.header.id, { branch: "main" });
|
|
133
|
-
expect(docA
|
|
130
|
+
expect(docA).toEqual(docB);
|
|
134
131
|
});
|
|
135
132
|
it("should sync operation from ReactorB to ReactorA via GqlChannel", async () => {
|
|
136
133
|
const document = driveDocumentModelModule.utils.createDocument();
|
|
134
|
+
await setupSyncForDrive(syncManagerA, document.header.id, resolverBridge);
|
|
137
135
|
const readyPromise = waitForOperationsReady(eventBusA, document.header.id);
|
|
138
136
|
const jobInfo = await reactorB.create(document);
|
|
139
137
|
await waitForJobCompletion(reactorB, jobInfo.id);
|
|
@@ -153,10 +151,9 @@ describe("Two-Reactor Sync with GqlChannel", () => {
|
|
|
153
151
|
}
|
|
154
152
|
const docA = await reactorA.get(document.header.id, { branch: "main" });
|
|
155
153
|
const docB = await reactorB.get(document.header.id, { branch: "main" });
|
|
156
|
-
expect(docA
|
|
154
|
+
expect(docA).toEqual(docB);
|
|
157
155
|
});
|
|
158
156
|
it("should sync multiple documents with concurrent operations from both reactors", async () => {
|
|
159
|
-
// Create 4 documents (2 for each reactor)
|
|
160
157
|
const docA1 = driveDocumentModelModule.utils.createDocument();
|
|
161
158
|
const docA2 = driveDocumentModelModule.utils.createDocument();
|
|
162
159
|
const docB1 = driveDocumentModelModule.utils.createDocument();
|
|
@@ -167,12 +164,16 @@ describe("Two-Reactor Sync with GqlChannel", () => {
|
|
|
167
164
|
docB1.header.id,
|
|
168
165
|
docB2.header.id,
|
|
169
166
|
];
|
|
170
|
-
|
|
167
|
+
await Promise.all([
|
|
168
|
+
setupSyncForDrive(syncManagerA, docA1.header.id, resolverBridge),
|
|
169
|
+
setupSyncForDrive(syncManagerA, docA2.header.id, resolverBridge),
|
|
170
|
+
setupSyncForDrive(syncManagerA, docB1.header.id, resolverBridge),
|
|
171
|
+
setupSyncForDrive(syncManagerA, docB2.header.id, resolverBridge),
|
|
172
|
+
]);
|
|
171
173
|
const readyOnB_A1 = waitForOperationsReady(eventBusB, docA1.header.id);
|
|
172
174
|
const readyOnB_A2 = waitForOperationsReady(eventBusB, docA2.header.id);
|
|
173
175
|
const readyOnA_B1 = waitForOperationsReady(eventBusA, docB1.header.id);
|
|
174
176
|
const readyOnA_B2 = waitForOperationsReady(eventBusA, docB2.header.id);
|
|
175
|
-
// Create documents on their respective reactors
|
|
176
177
|
const [jobA1, jobA2] = await Promise.all([
|
|
177
178
|
reactorA.create(docA1),
|
|
178
179
|
reactorA.create(docA2),
|
|
@@ -181,16 +182,13 @@ describe("Two-Reactor Sync with GqlChannel", () => {
|
|
|
181
182
|
reactorB.create(docB1),
|
|
182
183
|
reactorB.create(docB2),
|
|
183
184
|
]);
|
|
184
|
-
// Wait for all creates to complete on source reactors
|
|
185
185
|
await Promise.all([
|
|
186
186
|
waitForJobCompletion(reactorA, jobA1.id),
|
|
187
187
|
waitForJobCompletion(reactorA, jobA2.id),
|
|
188
188
|
waitForJobCompletion(reactorB, jobB1.id),
|
|
189
189
|
waitForJobCompletion(reactorB, jobB2.id),
|
|
190
190
|
]);
|
|
191
|
-
// Wait for all docs to sync to the other reactor
|
|
192
191
|
await Promise.all([readyOnB_A1, readyOnB_A2, readyOnA_B1, readyOnA_B2]);
|
|
193
|
-
// Now fire concurrent modify operations on each doc
|
|
194
192
|
void reactorA.execute(docA1.header.id, "main", [
|
|
195
193
|
driveDocumentModelModule.actions.setDriveName({ name: "Drive A1" }),
|
|
196
194
|
driveDocumentModelModule.actions.addFolder({
|
|
@@ -223,7 +221,6 @@ describe("Two-Reactor Sync with GqlChannel", () => {
|
|
|
223
221
|
parentFolder: null,
|
|
224
222
|
}),
|
|
225
223
|
]);
|
|
226
|
-
// Poll until all 4 documents are synced on both reactors
|
|
227
224
|
const startTime = Date.now();
|
|
228
225
|
const timeout = 25000;
|
|
229
226
|
let synced = false;
|
|
@@ -239,8 +236,6 @@ describe("Two-Reactor Sync with GqlChannel", () => {
|
|
|
239
236
|
branch: "main",
|
|
240
237
|
});
|
|
241
238
|
const opsB = Object.values(resultB).flatMap((scope) => scope.results);
|
|
242
|
-
// Each doc should have at least 2 ops (create + execute with actions)
|
|
243
|
-
// and both reactors should have same count
|
|
244
239
|
if (opsA.length < 2 ||
|
|
245
240
|
opsB.length < 2 ||
|
|
246
241
|
opsA.length !== opsB.length) {
|
|
@@ -249,7 +244,6 @@ describe("Two-Reactor Sync with GqlChannel", () => {
|
|
|
249
244
|
}
|
|
250
245
|
}
|
|
251
246
|
catch {
|
|
252
|
-
// Document may not exist on one reactor yet
|
|
253
247
|
allDocsSynced = false;
|
|
254
248
|
break;
|
|
255
249
|
}
|
|
@@ -261,7 +255,6 @@ describe("Two-Reactor Sync with GqlChannel", () => {
|
|
|
261
255
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
262
256
|
}
|
|
263
257
|
expect(synced).toBe(true);
|
|
264
|
-
// Verify operation equality for all 4 documents
|
|
265
258
|
for (const docId of allDocIds) {
|
|
266
259
|
const resultA = await reactorA.getOperations(docId, { branch: "main" });
|
|
267
260
|
const opsA = Object.values(resultA).flatMap((scope) => scope.results);
|
|
@@ -273,76 +266,70 @@ describe("Two-Reactor Sync with GqlChannel", () => {
|
|
|
273
266
|
expect(opsB[i]).toEqual(opsA[i]);
|
|
274
267
|
}
|
|
275
268
|
}
|
|
276
|
-
// Verify document state equality for all 4 documents
|
|
277
269
|
for (const docId of allDocIds) {
|
|
278
270
|
const docFromA = await reactorA.get(docId, { branch: "main" });
|
|
279
271
|
const docFromB = await reactorB.get(docId, { branch: "main" });
|
|
280
|
-
expect(docFromA
|
|
272
|
+
expect(docFromA).toEqual(docFromB);
|
|
281
273
|
}
|
|
282
274
|
}, 30000);
|
|
283
|
-
it("should
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
const
|
|
287
|
-
await
|
|
275
|
+
it("should include key/dependsOn in poll response for batch operations", async () => {
|
|
276
|
+
const driveDoc = driveDocumentModelModule.utils.createDocument();
|
|
277
|
+
await setupSyncForDrive(syncManagerA, driveDoc.header.id, resolverBridge);
|
|
278
|
+
const readyPromise = waitForOperationsReady(eventBusA, driveDoc.header.id);
|
|
279
|
+
const createJob = await reactorB.create(driveDoc);
|
|
280
|
+
await waitForJobCompletion(reactorB, createJob.id);
|
|
288
281
|
await readyPromise;
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
282
|
+
const batchResult = await reactorB.executeBatch({
|
|
283
|
+
jobs: [
|
|
284
|
+
{
|
|
285
|
+
key: "rename",
|
|
286
|
+
documentId: driveDoc.header.id,
|
|
287
|
+
scope: "global",
|
|
288
|
+
branch: "main",
|
|
289
|
+
actions: [
|
|
290
|
+
driveDocumentModelModule.actions.setDriveName({
|
|
291
|
+
name: "Renamed Drive",
|
|
292
|
+
}),
|
|
293
|
+
],
|
|
294
|
+
dependsOn: [],
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
key: "add-folder",
|
|
298
|
+
documentId: driveDoc.header.id,
|
|
299
|
+
scope: "global",
|
|
300
|
+
branch: "main",
|
|
301
|
+
actions: [
|
|
302
|
+
driveDocumentModelModule.actions.addFolder({
|
|
303
|
+
id: "folder-1",
|
|
304
|
+
name: "Folder 1",
|
|
305
|
+
parentFolder: null,
|
|
306
|
+
}),
|
|
307
|
+
],
|
|
308
|
+
dependsOn: ["rename"],
|
|
309
|
+
},
|
|
310
|
+
],
|
|
311
|
+
});
|
|
312
|
+
for (const jobInfo of Object.values(batchResult.jobs)) {
|
|
313
|
+
await waitForJobCompletion(reactorB, jobInfo.id);
|
|
314
|
+
}
|
|
311
315
|
const startTime = Date.now();
|
|
312
|
-
const timeout =
|
|
313
|
-
let
|
|
316
|
+
const timeout = 10000;
|
|
317
|
+
let statesSynced = false;
|
|
314
318
|
while (Date.now() - startTime < timeout) {
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
});
|
|
322
|
-
const opsB = Object.values(resultB).flatMap((scope) => scope.results);
|
|
323
|
-
if (opsA.length > 1 && opsB.length > 1 && opsA.length === opsB.length) {
|
|
324
|
-
synced = true;
|
|
319
|
+
const docA = await reactorA.get(driveDoc.header.id, { branch: "main" });
|
|
320
|
+
const docB = await reactorB.get(driveDoc.header.id, { branch: "main" });
|
|
321
|
+
if (docA &&
|
|
322
|
+
docB &&
|
|
323
|
+
JSON.stringify(docA.state) === JSON.stringify(docB.state)) {
|
|
324
|
+
statesSynced = true;
|
|
325
325
|
break;
|
|
326
326
|
}
|
|
327
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
328
|
-
}
|
|
329
|
-
expect(synced).toBe(true);
|
|
330
|
-
const resultA = await reactorA.getOperations(doc.header.id, {
|
|
331
|
-
branch: "main",
|
|
332
|
-
});
|
|
333
|
-
const opsA = Object.values(resultA).flatMap((scope) => scope.results);
|
|
334
|
-
const resultB = await reactorB.getOperations(doc.header.id, {
|
|
335
|
-
branch: "main",
|
|
336
|
-
});
|
|
337
|
-
const opsB = Object.values(resultB).flatMap((scope) => scope.results);
|
|
338
|
-
expect(opsA.length).toBeGreaterThan(0);
|
|
339
|
-
expect(opsB.length).toBe(opsA.length);
|
|
340
|
-
for (let i = 0; i < opsA.length; i++) {
|
|
341
|
-
expect(opsB[i]).toEqual(opsA[i]);
|
|
327
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
342
328
|
}
|
|
343
|
-
|
|
344
|
-
const
|
|
345
|
-
|
|
346
|
-
|
|
329
|
+
expect(statesSynced).toBe(true);
|
|
330
|
+
const docA = await reactorA.get(driveDoc.header.id, { branch: "main" });
|
|
331
|
+
const docB = await reactorB.get(driveDoc.header.id, { branch: "main" });
|
|
332
|
+
expect(docA.state).toEqual(docB.state);
|
|
333
|
+
}, 15000);
|
|
347
334
|
});
|
|
348
335
|
//# sourceMappingURL=two-reactor-gql-sync.test.js.map
|