@powerhousedao/reactor-api 5.1.0-dev.4 → 5.1.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.
Files changed (140) hide show
  1. package/dist/codegen.js +2 -2
  2. package/dist/codegen.js.map +1 -1
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +2 -0
  6. package/dist/index.js.map +1 -1
  7. package/dist/src/graphql/auth/index.d.ts +2 -0
  8. package/dist/src/graphql/auth/index.d.ts.map +1 -0
  9. package/dist/src/graphql/auth/index.js +2 -0
  10. package/dist/src/graphql/auth/index.js.map +1 -0
  11. package/dist/src/graphql/auth/resolvers.d.ts +149 -0
  12. package/dist/src/graphql/auth/resolvers.d.ts.map +1 -0
  13. package/dist/src/graphql/auth/resolvers.js +173 -0
  14. package/dist/src/graphql/auth/resolvers.js.map +1 -0
  15. package/dist/src/graphql/auth/schema.graphql +173 -0
  16. package/dist/src/graphql/auth/subgraph.d.ts +177 -0
  17. package/dist/src/graphql/auth/subgraph.d.ts.map +1 -0
  18. package/dist/src/graphql/auth/subgraph.js +340 -0
  19. package/dist/src/graphql/auth/subgraph.js.map +1 -0
  20. package/dist/src/graphql/base-subgraph.d.ts +3 -1
  21. package/dist/src/graphql/base-subgraph.d.ts.map +1 -1
  22. package/dist/src/graphql/base-subgraph.js +2 -0
  23. package/dist/src/graphql/base-subgraph.js.map +1 -1
  24. package/dist/src/graphql/document-model-subgraph.d.ts +51 -0
  25. package/dist/src/graphql/document-model-subgraph.d.ts.map +1 -0
  26. package/dist/src/graphql/document-model-subgraph.js +104 -0
  27. package/dist/src/graphql/document-model-subgraph.js.map +1 -0
  28. package/dist/src/graphql/drive-subgraph.d.ts.map +1 -1
  29. package/dist/src/graphql/drive-subgraph.js +52 -11
  30. package/dist/src/graphql/drive-subgraph.js.map +1 -1
  31. package/dist/src/graphql/graphql-manager.d.ts +11 -3
  32. package/dist/src/graphql/graphql-manager.d.ts.map +1 -1
  33. package/dist/src/graphql/graphql-manager.js +73 -16
  34. package/dist/src/graphql/graphql-manager.js.map +1 -1
  35. package/dist/src/graphql/index.d.ts +1 -0
  36. package/dist/src/graphql/index.d.ts.map +1 -1
  37. package/dist/src/graphql/index.js +1 -0
  38. package/dist/src/graphql/index.js.map +1 -1
  39. package/dist/src/graphql/reactor/adapters.d.ts +2 -2
  40. package/dist/src/graphql/reactor/adapters.d.ts.map +1 -1
  41. package/dist/src/graphql/reactor/adapters.js +3 -3
  42. package/dist/src/graphql/reactor/adapters.js.map +1 -1
  43. package/dist/src/graphql/reactor/factory.d.ts.map +1 -1
  44. package/dist/src/graphql/reactor/gen/graphql.d.ts +140 -89
  45. package/dist/src/graphql/reactor/gen/graphql.d.ts.map +1 -1
  46. package/dist/src/graphql/reactor/gen/graphql.js +46 -1
  47. package/dist/src/graphql/reactor/gen/graphql.js.map +1 -1
  48. package/dist/src/graphql/reactor/operations.graphql +253 -0
  49. package/dist/src/graphql/reactor/pubsub.d.ts +2 -2
  50. package/dist/src/graphql/reactor/pubsub.d.ts.map +1 -1
  51. package/dist/src/graphql/reactor/pubsub.js +2 -1
  52. package/dist/src/graphql/reactor/pubsub.js.map +1 -1
  53. package/dist/src/graphql/reactor/resolvers.d.ts +6 -1
  54. package/dist/src/graphql/reactor/resolvers.d.ts.map +1 -1
  55. package/dist/src/graphql/reactor/resolvers.js +67 -12
  56. package/dist/src/graphql/reactor/resolvers.js.map +1 -1
  57. package/dist/src/graphql/reactor/schema.graphql +429 -0
  58. package/dist/src/graphql/reactor/subgraph.d.ts +34 -0
  59. package/dist/src/graphql/reactor/subgraph.d.ts.map +1 -1
  60. package/dist/src/graphql/reactor/subgraph.js +266 -37
  61. package/dist/src/graphql/reactor/subgraph.js.map +1 -1
  62. package/dist/src/graphql/reactor/validation.d.ts +62 -62
  63. package/dist/src/graphql/system/system-subgraph.d.ts.map +1 -1
  64. package/dist/src/graphql/types.d.ts +4 -1
  65. package/dist/src/graphql/types.d.ts.map +1 -1
  66. package/dist/src/graphql/utils.d.ts.map +1 -1
  67. package/dist/src/graphql/utils.js +2 -2
  68. package/dist/src/graphql/utils.js.map +1 -1
  69. package/dist/src/migrations/001_create_document_permissions.d.ts +4 -0
  70. package/dist/src/migrations/001_create_document_permissions.d.ts.map +1 -0
  71. package/dist/src/migrations/001_create_document_permissions.js +91 -0
  72. package/dist/src/migrations/001_create_document_permissions.js.map +1 -0
  73. package/dist/src/migrations/index.d.ts +10 -0
  74. package/dist/src/migrations/index.d.ts.map +1 -0
  75. package/dist/src/migrations/index.js +56 -0
  76. package/dist/src/migrations/index.js.map +1 -0
  77. package/dist/src/packages/package-manager.d.ts +1 -1
  78. package/dist/src/packages/package-manager.d.ts.map +1 -1
  79. package/dist/src/packages/package-manager.js.map +1 -1
  80. package/dist/src/packages/util.js +1 -1
  81. package/dist/src/packages/util.js.map +1 -1
  82. package/dist/src/server.d.ts +17 -7
  83. package/dist/src/server.d.ts.map +1 -1
  84. package/dist/src/server.js +59 -22
  85. package/dist/src/server.js.map +1 -1
  86. package/dist/src/services/auth.service.d.ts +1 -0
  87. package/dist/src/services/auth.service.d.ts.map +1 -1
  88. package/dist/src/services/auth.service.js +30 -16
  89. package/dist/src/services/auth.service.js.map +1 -1
  90. package/dist/src/services/document-permission.service.d.ts +201 -0
  91. package/dist/src/services/document-permission.service.d.ts.map +1 -0
  92. package/dist/src/services/document-permission.service.js +636 -0
  93. package/dist/src/services/document-permission.service.js.map +1 -0
  94. package/dist/src/tracing.d.ts +4 -0
  95. package/dist/src/tracing.d.ts.map +1 -0
  96. package/dist/src/tracing.js +122 -0
  97. package/dist/src/tracing.js.map +1 -0
  98. package/dist/src/utils/create-schema.d.ts +5 -4
  99. package/dist/src/utils/create-schema.d.ts.map +1 -1
  100. package/dist/src/utils/create-schema.js +110 -4
  101. package/dist/src/utils/create-schema.js.map +1 -1
  102. package/dist/src/utils/db.d.ts +65 -1
  103. package/dist/src/utils/db.d.ts.map +1 -1
  104. package/dist/src/utils/db.js.map +1 -1
  105. package/dist/test/document-permission.service.test.d.ts +2 -0
  106. package/dist/test/document-permission.service.test.d.ts.map +1 -0
  107. package/dist/test/document-permission.service.test.js +480 -0
  108. package/dist/test/document-permission.service.test.js.map +1 -0
  109. package/dist/test/drive-subgraph-permissions.test.d.ts +2 -0
  110. package/dist/test/drive-subgraph-permissions.test.d.ts.map +1 -0
  111. package/dist/test/drive-subgraph-permissions.test.js +195 -0
  112. package/dist/test/drive-subgraph-permissions.test.js.map +1 -0
  113. package/dist/test/identity-integration.test.d.ts +2 -0
  114. package/dist/test/identity-integration.test.d.ts.map +1 -0
  115. package/dist/test/identity-integration.test.js +349 -0
  116. package/dist/test/identity-integration.test.js.map +1 -0
  117. package/dist/test/permissions-integration.test.d.ts +2 -0
  118. package/dist/test/permissions-integration.test.d.ts.map +1 -0
  119. package/dist/test/permissions-integration.test.js +421 -0
  120. package/dist/test/permissions-integration.test.js.map +1 -0
  121. package/dist/test/reactor-adapters.test.js +30 -137
  122. package/dist/test/reactor-adapters.test.js.map +1 -1
  123. package/dist/test/reactor-resolvers.test.js +157 -970
  124. package/dist/test/reactor-resolvers.test.js.map +1 -1
  125. package/dist/test/reactor-subgraph-permissions.test.d.ts +2 -0
  126. package/dist/test/reactor-subgraph-permissions.test.d.ts.map +1 -0
  127. package/dist/test/reactor-subgraph-permissions.test.js +400 -0
  128. package/dist/test/reactor-subgraph-permissions.test.js.map +1 -0
  129. package/dist/test/three-reactor-gql-sync.test.d.ts +2 -0
  130. package/dist/test/three-reactor-gql-sync.test.d.ts.map +1 -0
  131. package/dist/test/three-reactor-gql-sync.test.js +368 -0
  132. package/dist/test/three-reactor-gql-sync.test.js.map +1 -0
  133. package/dist/test/two-reactor-gql-sync.test.js +111 -59
  134. package/dist/test/two-reactor-gql-sync.test.js.map +1 -1
  135. package/dist/test/utils/gql-resolver-bridge.js +4 -4
  136. package/dist/test/utils/gql-resolver-bridge.js.map +1 -1
  137. package/dist/test/utils.d.ts.map +1 -1
  138. package/dist/test/utils.js.map +1 -1
  139. package/dist/tsconfig.tsbuildinfo +1 -1
  140. package/package.json +16 -8
@@ -0,0 +1,368 @@
1
+ import { CompositeChannelFactory, ConsoleLogger, JobStatus, OperationEventTypes, ReactorBuilder, ReactorClientBuilder, 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_MODELS_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(OperationEventTypes.OPERATIONS_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
+ async function waitForDocumentExists(reactor, documentId, timeoutMs = 10000) {
37
+ const startTime = Date.now();
38
+ while (Date.now() - startTime < timeoutMs) {
39
+ try {
40
+ await reactor.get(documentId);
41
+ return;
42
+ }
43
+ catch {
44
+ await new Promise((resolve) => setTimeout(resolve, 100));
45
+ }
46
+ }
47
+ throw new Error(`Document ${documentId} did not appear within ${timeoutMs}ms`);
48
+ }
49
+ async function pollUntilSynced(reactorA, reactorB, reactorC, documentId, timeoutMs = 15000) {
50
+ const startTime = Date.now();
51
+ while (Date.now() - startTime < timeoutMs) {
52
+ try {
53
+ const resultA = await reactorA.getOperations(documentId, {
54
+ branch: "main",
55
+ });
56
+ const opsA = Object.values(resultA).flatMap((scope) => scope.results);
57
+ const resultB = await reactorB.getOperations(documentId, {
58
+ branch: "main",
59
+ });
60
+ const opsB = Object.values(resultB).flatMap((scope) => scope.results);
61
+ const resultC = await reactorC.getOperations(documentId, {
62
+ branch: "main",
63
+ });
64
+ const opsC = Object.values(resultC).flatMap((scope) => scope.results);
65
+ if (opsA.length > 0 &&
66
+ opsA.length === opsB.length &&
67
+ opsB.length === opsC.length) {
68
+ return;
69
+ }
70
+ }
71
+ catch {
72
+ // Document may not exist yet on some reactors
73
+ }
74
+ await new Promise((resolve) => setTimeout(resolve, 100));
75
+ }
76
+ throw new Error(`Reactors did not sync document ${documentId} within ${timeoutMs}ms`);
77
+ }
78
+ async function setupThreeReactorsWithGqlChannel() {
79
+ const syncManagerRegistry = new Map();
80
+ const resolverBridge = createResolverBridge(syncManagerRegistry);
81
+ const logger = new ConsoleLogger(["test"]);
82
+ const channelFactorySwitchboard = new CompositeChannelFactory(logger);
83
+ const channelFactoryClient1 = new CompositeChannelFactory(logger);
84
+ const channelFactoryClient2 = new CompositeChannelFactory(logger);
85
+ const models = [
86
+ driveDocumentModelModule,
87
+ documentModelDocumentModelModule,
88
+ ];
89
+ const switchboardModule = await new ReactorClientBuilder()
90
+ .withReactorBuilder(new ReactorBuilder()
91
+ .withDocumentModels(models)
92
+ .withSync(new SyncBuilder().withChannelFactory(channelFactorySwitchboard)))
93
+ .buildModule();
94
+ const client1Module = await new ReactorClientBuilder()
95
+ .withReactorBuilder(new ReactorBuilder()
96
+ .withDocumentModels(models)
97
+ .withSync(new SyncBuilder().withChannelFactory(channelFactoryClient1)))
98
+ .buildModule();
99
+ const client2Module = await new ReactorClientBuilder()
100
+ .withReactorBuilder(new ReactorBuilder()
101
+ .withDocumentModels(models)
102
+ .withSync(new SyncBuilder().withChannelFactory(channelFactoryClient2)))
103
+ .buildModule();
104
+ const reactorSwitchboard = switchboardModule.reactor;
105
+ const reactorClient1 = client1Module.reactor;
106
+ const reactorClient2 = client2Module.reactor;
107
+ const eventBusSwitchboard = switchboardModule.eventBus;
108
+ const eventBusClient1 = client1Module.eventBus;
109
+ const eventBusClient2 = client2Module.eventBus;
110
+ const syncManagerSwitchboard = switchboardModule.reactorModule.syncModule.syncManager;
111
+ const syncManagerClient1 = client1Module.reactorModule.syncModule.syncManager;
112
+ const syncManagerClient2 = client2Module.reactorModule.syncModule.syncManager;
113
+ syncManagerRegistry.set("switchboard", syncManagerSwitchboard);
114
+ syncManagerRegistry.set("client1", syncManagerClient1);
115
+ syncManagerRegistry.set("client2", syncManagerClient2);
116
+ const filter = {
117
+ documentId: [],
118
+ scope: [],
119
+ branch: "main",
120
+ };
121
+ const gqlParamsToSwitchboard = {
122
+ url: "http://switchboard/graphql",
123
+ pollIntervalMs: 100,
124
+ maxFailures: 10,
125
+ retryBaseDelayMs: 50,
126
+ fetchFn: resolverBridge,
127
+ };
128
+ await syncManagerClient1.add("remoteSwitchboard", "collection1", { type: "gql", parameters: gqlParamsToSwitchboard }, filter);
129
+ await syncManagerClient2.add("remoteSwitchboard", "collection1", { type: "gql", parameters: gqlParamsToSwitchboard }, filter);
130
+ return {
131
+ switchboard: switchboardModule.client,
132
+ client1: client1Module.client,
133
+ client2: client2Module.client,
134
+ reactorSwitchboard,
135
+ reactorClient1,
136
+ reactorClient2,
137
+ eventBusSwitchboard,
138
+ eventBusClient1,
139
+ eventBusClient2,
140
+ syncManagerSwitchboard,
141
+ syncManagerClient1,
142
+ syncManagerClient2,
143
+ };
144
+ }
145
+ describe("Three-Reactor Sync (Switchboard Pattern)", () => {
146
+ let setup;
147
+ beforeEach(async () => {
148
+ setup = await setupThreeReactorsWithGqlChannel();
149
+ });
150
+ afterEach(() => {
151
+ setup.reactorSwitchboard.kill();
152
+ setup.reactorClient1.kill();
153
+ setup.reactorClient2.kill();
154
+ });
155
+ describe("Operation Ordering", () => {
156
+ it("should deliver CREATE_DOCUMENT before ADD_RELATIONSHIP for new documents", async () => {
157
+ const drive = driveDocumentModelModule.utils.createDocument();
158
+ const readyOnClient1 = waitForOperationsReady(setup.eventBusClient1, drive.header.id);
159
+ const readyOnClient2 = waitForOperationsReady(setup.eventBusClient2, drive.header.id);
160
+ const driveJob = await setup.reactorSwitchboard.create(drive);
161
+ await waitForJobCompletion(setup.reactorSwitchboard, driveJob.id);
162
+ await Promise.all([readyOnClient1, readyOnClient2]);
163
+ const newDoc = driveDocumentModelModule.utils.createDocument();
164
+ const doc = await setup.client1.createDocumentInDrive(drive.header.id, newDoc);
165
+ await waitForDocumentExists(setup.reactorClient2, doc.header.id, 10000);
166
+ const docOnClient2 = await setup.client2.get(doc.header.id);
167
+ expect(docOnClient2.document).toBeDefined();
168
+ expect(docOnClient2.document.header.id).toBe(doc.header.id);
169
+ });
170
+ it("should handle sequential document creation with immediate relationships", async () => {
171
+ const drive = driveDocumentModelModule.utils.createDocument();
172
+ const readyOnClient1 = waitForOperationsReady(setup.eventBusClient1, drive.header.id);
173
+ const readyOnClient2 = waitForOperationsReady(setup.eventBusClient2, drive.header.id);
174
+ const driveJob = await setup.reactorSwitchboard.create(drive);
175
+ await waitForJobCompletion(setup.reactorSwitchboard, driveJob.id);
176
+ await Promise.all([readyOnClient1, readyOnClient2]);
177
+ // Create documents sequentially (parallel creation in same drive causes revision conflicts)
178
+ const docs = [];
179
+ for (let i = 0; i < 3; i++) {
180
+ const doc = await setup.client1.createDocumentInDrive(drive.header.id, driveDocumentModelModule.utils.createDocument());
181
+ docs.push(doc);
182
+ }
183
+ for (const doc of docs) {
184
+ await waitForDocumentExists(setup.reactorClient2, doc.header.id, 15000);
185
+ const docOnClient2 = await setup.client2.get(doc.header.id);
186
+ expect(docOnClient2.document).toBeDefined();
187
+ }
188
+ }, 30000);
189
+ });
190
+ describe("Sequential Operations (Back-and-Forth)", () => {
191
+ it("should sync sequential edits: C1 -> SW -> C2 -> SW -> C1", async () => {
192
+ const drive = driveDocumentModelModule.utils.createDocument();
193
+ const readyOnClient1 = waitForOperationsReady(setup.eventBusClient1, drive.header.id);
194
+ const readyOnClient2 = waitForOperationsReady(setup.eventBusClient2, drive.header.id);
195
+ const driveJob = await setup.reactorSwitchboard.create(drive);
196
+ await waitForJobCompletion(setup.reactorSwitchboard, driveJob.id);
197
+ await Promise.all([readyOnClient1, readyOnClient2]);
198
+ await setup.client1.execute(drive.header.id, "main", [
199
+ driveDocumentModelModule.actions.setDriveName({ name: "Edit from C1" }),
200
+ ]);
201
+ await new Promise((resolve) => setTimeout(resolve, 500));
202
+ await setup.client2.execute(drive.header.id, "main", [
203
+ driveDocumentModelModule.actions.setDriveName({ name: "Edit from C2" }),
204
+ ]);
205
+ await new Promise((resolve) => setTimeout(resolve, 500));
206
+ await setup.client1.execute(drive.header.id, "main", [
207
+ driveDocumentModelModule.actions.setDriveName({
208
+ name: "Final edit from C1",
209
+ }),
210
+ ]);
211
+ await pollUntilSynced(setup.reactorSwitchboard, setup.reactorClient1, setup.reactorClient2, drive.header.id, 20000);
212
+ const resultS = await setup.reactorSwitchboard.getOperations(drive.header.id, { branch: "main" });
213
+ const opsS = Object.values(resultS).flatMap((scope) => scope.results);
214
+ const resultC1 = await setup.reactorClient1.getOperations(drive.header.id, { branch: "main" });
215
+ const opsC1 = Object.values(resultC1).flatMap((scope) => scope.results);
216
+ const resultC2 = await setup.reactorClient2.getOperations(drive.header.id, { branch: "main" });
217
+ const opsC2 = Object.values(resultC2).flatMap((scope) => scope.results);
218
+ expect(opsS.length).toBe(opsC1.length);
219
+ expect(opsC1.length).toBe(opsC2.length);
220
+ expect(opsS.length).toBeGreaterThanOrEqual(4);
221
+ }, 30000);
222
+ it("should not create duplicate operations", async () => {
223
+ const drive = driveDocumentModelModule.utils.createDocument();
224
+ const readyOnClient1 = waitForOperationsReady(setup.eventBusClient1, drive.header.id);
225
+ const readyOnClient2 = waitForOperationsReady(setup.eventBusClient2, drive.header.id);
226
+ const driveJob = await setup.reactorSwitchboard.create(drive);
227
+ await waitForJobCompletion(setup.reactorSwitchboard, driveJob.id);
228
+ await Promise.all([readyOnClient1, readyOnClient2]);
229
+ await setup.client1.execute(drive.header.id, "main", [
230
+ driveDocumentModelModule.actions.addFolder({
231
+ id: "folder-1",
232
+ name: "Folder 1",
233
+ parentFolder: null,
234
+ }),
235
+ ]);
236
+ await pollUntilSynced(setup.reactorSwitchboard, setup.reactorClient1, setup.reactorClient2, drive.header.id, 10000);
237
+ const resultC2 = await setup.reactorClient2.getOperations(drive.header.id, { branch: "main" });
238
+ const opsC2 = Object.values(resultC2).flatMap((scope) => scope.results);
239
+ const addFolderOps = opsC2.filter((op) => op.action?.type === "ADD_FOLDER");
240
+ expect(addFolderOps.length).toBe(1);
241
+ }, 15000);
242
+ });
243
+ describe("Near-Concurrent Operations", () => {
244
+ it("should handle edits made before full propagation", async () => {
245
+ const drive = driveDocumentModelModule.utils.createDocument();
246
+ const readyOnClient1 = waitForOperationsReady(setup.eventBusClient1, drive.header.id);
247
+ const readyOnClient2 = waitForOperationsReady(setup.eventBusClient2, drive.header.id);
248
+ const driveJob = await setup.reactorSwitchboard.create(drive);
249
+ await waitForJobCompletion(setup.reactorSwitchboard, driveJob.id);
250
+ await Promise.all([readyOnClient1, readyOnClient2]);
251
+ void setup.client1.execute(drive.header.id, "main", [
252
+ driveDocumentModelModule.actions.addFolder({
253
+ id: "folder-c1",
254
+ name: "Folder from C1",
255
+ parentFolder: null,
256
+ }),
257
+ ]);
258
+ void setup.client2.execute(drive.header.id, "main", [
259
+ driveDocumentModelModule.actions.addFolder({
260
+ id: "folder-c2",
261
+ name: "Folder from C2",
262
+ parentFolder: null,
263
+ }),
264
+ ]);
265
+ await pollUntilSynced(setup.reactorSwitchboard, setup.reactorClient1, setup.reactorClient2, drive.header.id, 20000);
266
+ const docS = await setup.switchboard.get(drive.header.id);
267
+ const docC1 = await setup.client1.get(drive.header.id);
268
+ const docC2 = await setup.client2.get(drive.header.id);
269
+ // Compare state (semantic equality) rather than full document
270
+ expect(docC1.document.state).toEqual(docS.document.state);
271
+ expect(docC2.document.state).toEqual(docS.document.state);
272
+ }, 25000);
273
+ it("should converge to same state on all reactors", async () => {
274
+ const drive = driveDocumentModelModule.utils.createDocument();
275
+ const readyOnClient1 = waitForOperationsReady(setup.eventBusClient1, drive.header.id);
276
+ const readyOnClient2 = waitForOperationsReady(setup.eventBusClient2, drive.header.id);
277
+ const driveJob = await setup.reactorSwitchboard.create(drive);
278
+ await waitForJobCompletion(setup.reactorSwitchboard, driveJob.id);
279
+ await Promise.all([readyOnClient1, readyOnClient2]);
280
+ void setup.client1.execute(drive.header.id, "main", [
281
+ driveDocumentModelModule.actions.setDriveName({ name: "Name from C1" }),
282
+ ]);
283
+ void setup.client2.execute(drive.header.id, "main", [
284
+ driveDocumentModelModule.actions.setDriveName({ name: "Name from C2" }),
285
+ ]);
286
+ await pollUntilSynced(setup.reactorSwitchboard, setup.reactorClient1, setup.reactorClient2, drive.header.id, 20000);
287
+ const docS = await setup.switchboard.get(drive.header.id);
288
+ const docC1 = await setup.client1.get(drive.header.id);
289
+ const docC2 = await setup.client2.get(drive.header.id);
290
+ // Compare state (semantic equality) rather than full document
291
+ expect(docC1.document.state).toEqual(docS.document.state);
292
+ expect(docC2.document.state).toEqual(docS.document.state);
293
+ }, 25000);
294
+ it("should handle concurrent edits at same index without opId conflicts", async () => {
295
+ const drive = driveDocumentModelModule.utils.createDocument();
296
+ const readyOnClient1 = waitForOperationsReady(setup.eventBusClient1, drive.header.id);
297
+ const readyOnClient2 = waitForOperationsReady(setup.eventBusClient2, drive.header.id);
298
+ const driveJob = await setup.reactorSwitchboard.create(drive);
299
+ await waitForJobCompletion(setup.reactorSwitchboard, driveJob.id);
300
+ await Promise.all([readyOnClient1, readyOnClient2]);
301
+ // CONFLICT: Both clients edit at same index concurrently (before sync)
302
+ // This triggers the scenario where duplicate load jobs may be created
303
+ // for the same incoming operation, causing "opId already exists" errors
304
+ void setup.client1.execute(drive.header.id, "main", [
305
+ driveDocumentModelModule.actions.setDriveName({ name: "Edit from C1" }),
306
+ ]);
307
+ void setup.client2.execute(drive.header.id, "main", [
308
+ driveDocumentModelModule.actions.setDriveName({ name: "Edit from C2" }),
309
+ ]);
310
+ // Wait for sync to complete - should NOT throw opId errors
311
+ await pollUntilSynced(setup.reactorSwitchboard, setup.reactorClient1, setup.reactorClient2, drive.header.id, 20000);
312
+ // Verify all reactors converged to same state
313
+ const docS = await setup.switchboard.get(drive.header.id);
314
+ const docC1 = await setup.client1.get(drive.header.id);
315
+ const docC2 = await setup.client2.get(drive.header.id);
316
+ expect(docC1.document.state).toEqual(docS.document.state);
317
+ expect(docC2.document.state).toEqual(docS.document.state);
318
+ }, 25000);
319
+ });
320
+ describe("Stress Tests", () => {
321
+ it("should handle rapid back-and-forth editing", async () => {
322
+ const drive = driveDocumentModelModule.utils.createDocument();
323
+ const readyOnClient1 = waitForOperationsReady(setup.eventBusClient1, drive.header.id);
324
+ const readyOnClient2 = waitForOperationsReady(setup.eventBusClient2, drive.header.id);
325
+ const driveJob = await setup.reactorSwitchboard.create(drive);
326
+ await waitForJobCompletion(setup.reactorSwitchboard, driveJob.id);
327
+ await Promise.all([readyOnClient1, readyOnClient2]);
328
+ // Use longer delays to allow sync to complete between operations
329
+ for (let i = 0; i < 5; i++) {
330
+ await setup.client1.execute(drive.header.id, "main", [
331
+ driveDocumentModelModule.actions.addFolder({
332
+ id: `folder-c1-${i}`,
333
+ name: `Folder C1 ${i}`,
334
+ parentFolder: null,
335
+ }),
336
+ ]);
337
+ // Wait for sync to complete before next operation
338
+ await new Promise((resolve) => setTimeout(resolve, 300));
339
+ await setup.client2.execute(drive.header.id, "main", [
340
+ driveDocumentModelModule.actions.addFolder({
341
+ id: `folder-c2-${i}`,
342
+ name: `Folder C2 ${i}`,
343
+ parentFolder: null,
344
+ }),
345
+ ]);
346
+ await new Promise((resolve) => setTimeout(resolve, 300));
347
+ }
348
+ await pollUntilSynced(setup.reactorSwitchboard, setup.reactorClient1, setup.reactorClient2, drive.header.id, 30000);
349
+ const resultS = await setup.reactorSwitchboard.getOperations(drive.header.id, { branch: "main" });
350
+ const opsS = Object.values(resultS).flatMap((scope) => scope.results);
351
+ const resultC1 = await setup.reactorClient1.getOperations(drive.header.id, { branch: "main" });
352
+ const opsC1 = Object.values(resultC1).flatMap((scope) => scope.results);
353
+ const resultC2 = await setup.reactorClient2.getOperations(drive.header.id, { branch: "main" });
354
+ const opsC2 = Object.values(resultC2).flatMap((scope) => scope.results);
355
+ expect(opsS.length).toBe(opsC1.length);
356
+ expect(opsC1.length).toBe(opsC2.length);
357
+ expect(opsS.length).toBeGreaterThanOrEqual(11);
358
+ const docS = await setup.switchboard.get(drive.header.id);
359
+ const docC1 = await setup.client1.get(drive.header.id);
360
+ const docC2 = await setup.client2.get(drive.header.id);
361
+ // Compare state (semantic equality) rather than full document
362
+ // Operation history may have format differences (e.g., signature serialization)
363
+ expect(docC1.document.state).toEqual(docS.document.state);
364
+ expect(docC2.document.state).toEqual(docS.document.state);
365
+ }, 60000);
366
+ });
367
+ });
368
+ //# sourceMappingURL=three-reactor-gql-sync.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"three-reactor-gql-sync.test.js","sourceRoot":"","sources":["../../test/three-reactor-gql-sync.test.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,aAAa,EACb,SAAS,EACT,mBAAmB,EACnB,cAAc,EACd,oBAAoB,EACpB,WAAW,GAOZ,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EACL,gCAAgC,GAEjC,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACrE,OAAO,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAiBtE,KAAK,UAAU,oBAAoB,CACjC,OAAiB,EACjB,KAAa,EACb,SAAS,GAAG,IAAI;IAEhB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAE7B,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,GAAG,SAAS,EAAE,CAAC;QAC1C,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;QAEjD,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,CAAC,iBAAiB,EAAE,CAAC;YAClD,OAAO;QACT,CAAC;QAED,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,CAAC,MAAM,EAAE,CAAC;YACvC,MAAM,IAAI,KAAK,CAAC,eAAe,MAAM,CAAC,KAAK,EAAE,OAAO,IAAI,SAAS,EAAE,CAAC,CAAC;QACvE,CAAC;QAED,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;IAC1D,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,+BAA+B,SAAS,IAAI,CAAC,CAAC;AAChE,CAAC;AAED,KAAK,UAAU,sBAAsB,CACnC,QAAmB,EACnB,UAAkB,EAClB,SAAS,GAAG,IAAI;IAEhB,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC3C,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;YAC9B,WAAW,EAAE,CAAC;YACd,MAAM,CACJ,IAAI,KAAK,CACP,uCAAuC,UAAU,wBAAwB,SAAS,IAAI,CACvF,CACF,CAAC;QACJ,CAAC,EAAE,SAAS,CAAC,CAAC;QAEd,MAAM,WAAW,GAAG,QAAQ,CAAC,SAAS,CACpC,mBAAmB,CAAC,gBAAgB,EACpC,CAAC,IAAY,EAAE,KAA2B,EAAE,EAAE;YAC5C,MAAM,WAAW,GAAG,KAAK,CAAC,UAAU,CAAC,IAAI,CACvC,CAAC,EAAwB,EAAE,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,UAAU,KAAK,UAAU,CACnE,CAAC;YAEF,IAAI,WAAW,EAAE,CAAC;gBAChB,YAAY,CAAC,OAAO,CAAC,CAAC;gBACtB,WAAW,EAAE,CAAC;gBACd,OAAO,EAAE,CAAC;YACZ,CAAC;QACH,CAAC,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,qBAAqB,CAClC,OAAiB,EACjB,UAAkB,EAClB,SAAS,GAAG,KAAK;IAEjB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAE7B,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,GAAG,SAAS,EAAE,CAAC;QAC1C,IAAI,CAAC;YACH,MAAM,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YAC9B,OAAO;QACT,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC;IAED,MAAM,IAAI,KAAK,CACb,YAAY,UAAU,0BAA0B,SAAS,IAAI,CAC9D,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,eAAe,CAC5B,QAAkB,EAClB,QAAkB,EAClB,QAAkB,EAClB,UAAkB,EAClB,SAAS,GAAG,KAAK;IAEjB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAE7B,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,GAAG,SAAS,EAAE,CAAC;QAC1C,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,aAAa,CAAC,UAAU,EAAE;gBACvD,MAAM,EAAE,MAAM;aACf,CAAC,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAEtE,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,aAAa,CAAC,UAAU,EAAE;gBACvD,MAAM,EAAE,MAAM;aACf,CAAC,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAEtE,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,aAAa,CAAC,UAAU,EAAE;gBACvD,MAAM,EAAE,MAAM;aACf,CAAC,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAEtE,IACE,IAAI,CAAC,MAAM,GAAG,CAAC;gBACf,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM;gBAC3B,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,EAC3B,CAAC;gBACD,OAAO;YACT,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,8CAA8C;QAChD,CAAC;QAED,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;IAC3D,CAAC;IAED,MAAM,IAAI,KAAK,CACb,kCAAkC,UAAU,WAAW,SAAS,IAAI,CACrE,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,gCAAgC;IAC7C,MAAM,mBAAmB,GAAG,IAAI,GAAG,EAAwB,CAAC;IAC5D,MAAM,cAAc,GAAG,oBAAoB,CAAC,mBAAmB,CAAC,CAAC;IAEjE,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IAC3C,MAAM,yBAAyB,GAAG,IAAI,uBAAuB,CAAC,MAAM,CAAC,CAAC;IACtE,MAAM,qBAAqB,GAAG,IAAI,uBAAuB,CAAC,MAAM,CAAC,CAAC;IAClE,MAAM,qBAAqB,GAAG,IAAI,uBAAuB,CAAC,MAAM,CAAC,CAAC;IAElE,MAAM,MAAM,GAAG;QACb,wBAAwB;QACxB,gCAAgC;KACH,CAAC;IAEhC,MAAM,iBAAiB,GAAG,MAAM,IAAI,oBAAoB,EAAE;SACvD,kBAAkB,CACjB,IAAI,cAAc,EAAE;SACjB,kBAAkB,CAAC,MAAM,CAAC;SAC1B,QAAQ,CACP,IAAI,WAAW,EAAE,CAAC,kBAAkB,CAAC,yBAAyB,CAAC,CAChE,CACJ;SACA,WAAW,EAAE,CAAC;IAEjB,MAAM,aAAa,GAAG,MAAM,IAAI,oBAAoB,EAAE;SACnD,kBAAkB,CACjB,IAAI,cAAc,EAAE;SACjB,kBAAkB,CAAC,MAAM,CAAC;SAC1B,QAAQ,CAAC,IAAI,WAAW,EAAE,CAAC,kBAAkB,CAAC,qBAAqB,CAAC,CAAC,CACzE;SACA,WAAW,EAAE,CAAC;IAEjB,MAAM,aAAa,GAAG,MAAM,IAAI,oBAAoB,EAAE;SACnD,kBAAkB,CACjB,IAAI,cAAc,EAAE;SACjB,kBAAkB,CAAC,MAAM,CAAC;SAC1B,QAAQ,CAAC,IAAI,WAAW,EAAE,CAAC,kBAAkB,CAAC,qBAAqB,CAAC,CAAC,CACzE;SACA,WAAW,EAAE,CAAC;IAEjB,MAAM,kBAAkB,GAAG,iBAAiB,CAAC,OAAO,CAAC;IACrD,MAAM,cAAc,GAAG,aAAa,CAAC,OAAO,CAAC;IAC7C,MAAM,cAAc,GAAG,aAAa,CAAC,OAAO,CAAC;IAE7C,MAAM,mBAAmB,GAAG,iBAAiB,CAAC,QAAQ,CAAC;IACvD,MAAM,eAAe,GAAG,aAAa,CAAC,QAAQ,CAAC;IAC/C,MAAM,eAAe,GAAG,aAAa,CAAC,QAAQ,CAAC;IAE/C,MAAM,sBAAsB,GAC1B,iBAAiB,CAAC,aAAc,CAAC,UAAW,CAAC,WAAW,CAAC;IAC3D,MAAM,kBAAkB,GACtB,aAAa,CAAC,aAAc,CAAC,UAAW,CAAC,WAAW,CAAC;IACvD,MAAM,kBAAkB,GACtB,aAAa,CAAC,aAAc,CAAC,UAAW,CAAC,WAAW,CAAC;IAEvD,mBAAmB,CAAC,GAAG,CAAC,aAAa,EAAE,sBAAsB,CAAC,CAAC;IAC/D,mBAAmB,CAAC,GAAG,CAAC,SAAS,EAAE,kBAAkB,CAAC,CAAC;IACvD,mBAAmB,CAAC,GAAG,CAAC,SAAS,EAAE,kBAAkB,CAAC,CAAC;IAEvD,MAAM,MAAM,GAAG;QACb,UAAU,EAAE,EAAE;QACd,KAAK,EAAE,EAAE;QACT,MAAM,EAAE,MAAM;KACf,CAAC;IAEF,MAAM,sBAAsB,GAAG;QAC7B,GAAG,EAAE,4BAA4B;QACjC,cAAc,EAAE,GAAG;QACnB,WAAW,EAAE,EAAE;QACf,gBAAgB,EAAE,EAAE;QACpB,OAAO,EAAE,cAAc;KACxB,CAAC;IAEF,MAAM,kBAAkB,CAAC,GAAG,CAC1B,mBAAmB,EACnB,aAAa,EACb,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,sBAAsB,EAAE,EACnD,MAAM,CACP,CAAC;IAEF,MAAM,kBAAkB,CAAC,GAAG,CAC1B,mBAAmB,EACnB,aAAa,EACb,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,sBAAsB,EAAE,EACnD,MAAM,CACP,CAAC;IAEF,OAAO;QACL,WAAW,EAAE,iBAAiB,CAAC,MAAM;QACrC,OAAO,EAAE,aAAa,CAAC,MAAM;QAC7B,OAAO,EAAE,aAAa,CAAC,MAAM;QAC7B,kBAAkB;QAClB,cAAc;QACd,cAAc;QACd,mBAAmB;QACnB,eAAe;QACf,eAAe;QACf,sBAAsB;QACtB,kBAAkB;QAClB,kBAAkB;KACnB,CAAC;AACJ,CAAC;AAED,QAAQ,CAAC,0CAA0C,EAAE,GAAG,EAAE;IACxD,IAAI,KAAwB,CAAC;IAE7B,UAAU,CAAC,KAAK,IAAI,EAAE;QACpB,KAAK,GAAG,MAAM,gCAAgC,EAAE,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACb,KAAK,CAAC,kBAAkB,CAAC,IAAI,EAAE,CAAC;QAChC,KAAK,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;QAC5B,KAAK,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;IAC9B,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;QAClC,EAAE,CAAC,0EAA0E,EAAE,KAAK,IAAI,EAAE;YACxF,MAAM,KAAK,GAAG,wBAAwB,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC;YAE9D,MAAM,cAAc,GAAG,sBAAsB,CAC3C,KAAK,CAAC,eAAe,EACrB,KAAK,CAAC,MAAM,CAAC,EAAE,CAChB,CAAC;YACF,MAAM,cAAc,GAAG,sBAAsB,CAC3C,KAAK,CAAC,eAAe,EACrB,KAAK,CAAC,MAAM,CAAC,EAAE,CAChB,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,kBAAkB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC9D,MAAM,oBAAoB,CAAC,KAAK,CAAC,kBAAkB,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;YAClE,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,cAAc,EAAE,cAAc,CAAC,CAAC,CAAC;YAEpD,MAAM,MAAM,GAAG,wBAAwB,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC;YAE/D,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,qBAAqB,CACnD,KAAK,CAAC,MAAM,CAAC,EAAE,EACf,MAAM,CACP,CAAC;YAEF,MAAM,qBAAqB,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;YAExE,MAAM,YAAY,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC5D,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;YAC5C,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC9D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,yEAAyE,EAAE,KAAK,IAAI,EAAE;YACvF,MAAM,KAAK,GAAG,wBAAwB,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC;YAE9D,MAAM,cAAc,GAAG,sBAAsB,CAC3C,KAAK,CAAC,eAAe,EACrB,KAAK,CAAC,MAAM,CAAC,EAAE,CAChB,CAAC;YACF,MAAM,cAAc,GAAG,sBAAsB,CAC3C,KAAK,CAAC,eAAe,EACrB,KAAK,CAAC,MAAM,CAAC,EAAE,CAChB,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,kBAAkB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC9D,MAAM,oBAAoB,CAAC,KAAK,CAAC,kBAAkB,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;YAClE,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,cAAc,EAAE,cAAc,CAAC,CAAC,CAAC;YAEpD,4FAA4F;YAC5F,MAAM,IAAI,GAAG,EAAE,CAAC;YAChB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;gBAC3B,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,qBAAqB,CACnD,KAAK,CAAC,MAAM,CAAC,EAAE,EACf,wBAAwB,CAAC,KAAK,CAAC,cAAc,EAAE,CAChD,CAAC;gBACF,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACjB,CAAC;YAED,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;gBACvB,MAAM,qBAAqB,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;gBACxE,MAAM,YAAY,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;gBAC5D,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;YAC9C,CAAC;QACH,CAAC,EAAE,KAAK,CAAC,CAAC;IACZ,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,wCAAwC,EAAE,GAAG,EAAE;QACtD,EAAE,CAAC,0DAA0D,EAAE,KAAK,IAAI,EAAE;YACxE,MAAM,KAAK,GAAG,wBAAwB,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC;YAE9D,MAAM,cAAc,GAAG,sBAAsB,CAC3C,KAAK,CAAC,eAAe,EACrB,KAAK,CAAC,MAAM,CAAC,EAAE,CAChB,CAAC;YACF,MAAM,cAAc,GAAG,sBAAsB,CAC3C,KAAK,CAAC,eAAe,EACrB,KAAK,CAAC,MAAM,CAAC,EAAE,CAChB,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,kBAAkB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC9D,MAAM,oBAAoB,CAAC,KAAK,CAAC,kBAAkB,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;YAClE,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,cAAc,EAAE,cAAc,CAAC,CAAC,CAAC;YAEpD,MAAM,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE;gBACnD,wBAAwB,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC;aACxE,CAAC,CAAC;YAEH,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;YAEzD,MAAM,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE;gBACnD,wBAAwB,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC;aACxE,CAAC,CAAC;YAEH,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;YAEzD,MAAM,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE;gBACnD,wBAAwB,CAAC,OAAO,CAAC,YAAY,CAAC;oBAC5C,IAAI,EAAE,oBAAoB;iBAC3B,CAAC;aACH,CAAC,CAAC;YAEH,MAAM,eAAe,CACnB,KAAK,CAAC,kBAAkB,EACxB,KAAK,CAAC,cAAc,EACpB,KAAK,CAAC,cAAc,EACpB,KAAK,CAAC,MAAM,CAAC,EAAE,EACf,KAAK,CACN,CAAC;YAEF,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,kBAAkB,CAAC,aAAa,CAC1D,KAAK,CAAC,MAAM,CAAC,EAAE,EACf,EAAE,MAAM,EAAE,MAAM,EAAE,CACnB,CAAC;YACF,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAEtE,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,cAAc,CAAC,aAAa,CACvD,KAAK,CAAC,MAAM,CAAC,EAAE,EACf,EAAE,MAAM,EAAE,MAAM,EAAE,CACnB,CAAC;YACF,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAExE,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,cAAc,CAAC,aAAa,CACvD,KAAK,CAAC,MAAM,CAAC,EAAE,EACf,EAAE,MAAM,EAAE,MAAM,EAAE,CACnB,CAAC;YACF,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAExE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YACvC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YACxC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC;QAChD,CAAC,EAAE,KAAK,CAAC,CAAC;QAEV,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;YACtD,MAAM,KAAK,GAAG,wBAAwB,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC;YAE9D,MAAM,cAAc,GAAG,sBAAsB,CAC3C,KAAK,CAAC,eAAe,EACrB,KAAK,CAAC,MAAM,CAAC,EAAE,CAChB,CAAC;YACF,MAAM,cAAc,GAAG,sBAAsB,CAC3C,KAAK,CAAC,eAAe,EACrB,KAAK,CAAC,MAAM,CAAC,EAAE,CAChB,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,kBAAkB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC9D,MAAM,oBAAoB,CAAC,KAAK,CAAC,kBAAkB,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;YAClE,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,cAAc,EAAE,cAAc,CAAC,CAAC,CAAC;YAEpD,MAAM,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE;gBACnD,wBAAwB,CAAC,OAAO,CAAC,SAAS,CAAC;oBACzC,EAAE,EAAE,UAAU;oBACd,IAAI,EAAE,UAAU;oBAChB,YAAY,EAAE,IAAI;iBACnB,CAAC;aACH,CAAC,CAAC;YAEH,MAAM,eAAe,CACnB,KAAK,CAAC,kBAAkB,EACxB,KAAK,CAAC,cAAc,EACpB,KAAK,CAAC,cAAc,EACpB,KAAK,CAAC,MAAM,CAAC,EAAE,EACf,KAAK,CACN,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,cAAc,CAAC,aAAa,CACvD,KAAK,CAAC,MAAM,CAAC,EAAE,EACf,EAAE,MAAM,EAAE,MAAM,EAAE,CACnB,CAAC;YACF,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAExE,MAAM,YAAY,GAAG,KAAK,CAAC,MAAM,CAC/B,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,KAAK,YAAY,CACzC,CAAC;YACF,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACtC,CAAC,EAAE,KAAK,CAAC,CAAC;IACZ,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,4BAA4B,EAAE,GAAG,EAAE;QAC1C,EAAE,CAAC,kDAAkD,EAAE,KAAK,IAAI,EAAE;YAChE,MAAM,KAAK,GAAG,wBAAwB,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC;YAE9D,MAAM,cAAc,GAAG,sBAAsB,CAC3C,KAAK,CAAC,eAAe,EACrB,KAAK,CAAC,MAAM,CAAC,EAAE,CAChB,CAAC;YACF,MAAM,cAAc,GAAG,sBAAsB,CAC3C,KAAK,CAAC,eAAe,EACrB,KAAK,CAAC,MAAM,CAAC,EAAE,CAChB,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,kBAAkB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC9D,MAAM,oBAAoB,CAAC,KAAK,CAAC,kBAAkB,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;YAClE,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,cAAc,EAAE,cAAc,CAAC,CAAC,CAAC;YAEpD,KAAK,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE;gBAClD,wBAAwB,CAAC,OAAO,CAAC,SAAS,CAAC;oBACzC,EAAE,EAAE,WAAW;oBACf,IAAI,EAAE,gBAAgB;oBACtB,YAAY,EAAE,IAAI;iBACnB,CAAC;aACH,CAAC,CAAC;YAEH,KAAK,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE;gBAClD,wBAAwB,CAAC,OAAO,CAAC,SAAS,CAAC;oBACzC,EAAE,EAAE,WAAW;oBACf,IAAI,EAAE,gBAAgB;oBACtB,YAAY,EAAE,IAAI;iBACnB,CAAC;aACH,CAAC,CAAC;YAEH,MAAM,eAAe,CACnB,KAAK,CAAC,kBAAkB,EACxB,KAAK,CAAC,cAAc,EACpB,KAAK,CAAC,cAAc,EACpB,KAAK,CAAC,MAAM,CAAC,EAAE,EACf,KAAK,CACN,CAAC;YAEF,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC1D,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACvD,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAEvD,8DAA8D;YAC9D,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;YAC1D,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC5D,CAAC,EAAE,KAAK,CAAC,CAAC;QAEV,EAAE,CAAC,+CAA+C,EAAE,KAAK,IAAI,EAAE;YAC7D,MAAM,KAAK,GAAG,wBAAwB,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC;YAE9D,MAAM,cAAc,GAAG,sBAAsB,CAC3C,KAAK,CAAC,eAAe,EACrB,KAAK,CAAC,MAAM,CAAC,EAAE,CAChB,CAAC;YACF,MAAM,cAAc,GAAG,sBAAsB,CAC3C,KAAK,CAAC,eAAe,EACrB,KAAK,CAAC,MAAM,CAAC,EAAE,CAChB,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,kBAAkB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC9D,MAAM,oBAAoB,CAAC,KAAK,CAAC,kBAAkB,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;YAClE,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,cAAc,EAAE,cAAc,CAAC,CAAC,CAAC;YAEpD,KAAK,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE;gBAClD,wBAAwB,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC;aACxE,CAAC,CAAC;YACH,KAAK,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE;gBAClD,wBAAwB,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC;aACxE,CAAC,CAAC;YAEH,MAAM,eAAe,CACnB,KAAK,CAAC,kBAAkB,EACxB,KAAK,CAAC,cAAc,EACpB,KAAK,CAAC,cAAc,EACpB,KAAK,CAAC,MAAM,CAAC,EAAE,EACf,KAAK,CACN,CAAC;YAEF,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC1D,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACvD,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAEvD,8DAA8D;YAC9D,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;YAC1D,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC5D,CAAC,EAAE,KAAK,CAAC,CAAC;QAEV,EAAE,CAAC,qEAAqE,EAAE,KAAK,IAAI,EAAE;YACnF,MAAM,KAAK,GAAG,wBAAwB,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC;YAE9D,MAAM,cAAc,GAAG,sBAAsB,CAC3C,KAAK,CAAC,eAAe,EACrB,KAAK,CAAC,MAAM,CAAC,EAAE,CAChB,CAAC;YACF,MAAM,cAAc,GAAG,sBAAsB,CAC3C,KAAK,CAAC,eAAe,EACrB,KAAK,CAAC,MAAM,CAAC,EAAE,CAChB,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,kBAAkB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC9D,MAAM,oBAAoB,CAAC,KAAK,CAAC,kBAAkB,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;YAClE,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,cAAc,EAAE,cAAc,CAAC,CAAC,CAAC;YAEpD,uEAAuE;YACvE,sEAAsE;YACtE,wEAAwE;YACxE,KAAK,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE;gBAClD,wBAAwB,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC;aACxE,CAAC,CAAC;YAEH,KAAK,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE;gBAClD,wBAAwB,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC;aACxE,CAAC,CAAC;YAEH,2DAA2D;YAC3D,MAAM,eAAe,CACnB,KAAK,CAAC,kBAAkB,EACxB,KAAK,CAAC,cAAc,EACpB,KAAK,CAAC,cAAc,EACpB,KAAK,CAAC,MAAM,CAAC,EAAE,EACf,KAAK,CACN,CAAC;YAEF,8CAA8C;YAC9C,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC1D,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACvD,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAEvD,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;YAC1D,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC5D,CAAC,EAAE,KAAK,CAAC,CAAC;IACZ,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;QAC5B,EAAE,CAAC,4CAA4C,EAAE,KAAK,IAAI,EAAE;YAC1D,MAAM,KAAK,GAAG,wBAAwB,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC;YAE9D,MAAM,cAAc,GAAG,sBAAsB,CAC3C,KAAK,CAAC,eAAe,EACrB,KAAK,CAAC,MAAM,CAAC,EAAE,CAChB,CAAC;YACF,MAAM,cAAc,GAAG,sBAAsB,CAC3C,KAAK,CAAC,eAAe,EACrB,KAAK,CAAC,MAAM,CAAC,EAAE,CAChB,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,kBAAkB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC9D,MAAM,oBAAoB,CAAC,KAAK,CAAC,kBAAkB,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;YAClE,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,cAAc,EAAE,cAAc,CAAC,CAAC,CAAC;YAEpD,iEAAiE;YACjE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;gBAC3B,MAAM,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE;oBACnD,wBAAwB,CAAC,OAAO,CAAC,SAAS,CAAC;wBACzC,EAAE,EAAE,aAAa,CAAC,EAAE;wBACpB,IAAI,EAAE,aAAa,CAAC,EAAE;wBACtB,YAAY,EAAE,IAAI;qBACnB,CAAC;iBACH,CAAC,CAAC;gBACH,kDAAkD;gBAClD,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;gBAEzD,MAAM,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE;oBACnD,wBAAwB,CAAC,OAAO,CAAC,SAAS,CAAC;wBACzC,EAAE,EAAE,aAAa,CAAC,EAAE;wBACpB,IAAI,EAAE,aAAa,CAAC,EAAE;wBACtB,YAAY,EAAE,IAAI;qBACnB,CAAC;iBACH,CAAC,CAAC;gBACH,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;YAC3D,CAAC;YAED,MAAM,eAAe,CACnB,KAAK,CAAC,kBAAkB,EACxB,KAAK,CAAC,cAAc,EACpB,KAAK,CAAC,cAAc,EACpB,KAAK,CAAC,MAAM,CAAC,EAAE,EACf,KAAK,CACN,CAAC;YAEF,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,kBAAkB,CAAC,aAAa,CAC1D,KAAK,CAAC,MAAM,CAAC,EAAE,EACf,EAAE,MAAM,EAAE,MAAM,EAAE,CACnB,CAAC;YACF,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAEtE,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,cAAc,CAAC,aAAa,CACvD,KAAK,CAAC,MAAM,CAAC,EAAE,EACf,EAAE,MAAM,EAAE,MAAM,EAAE,CACnB,CAAC;YACF,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAExE,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,cAAc,CAAC,aAAa,CACvD,KAAK,CAAC,MAAM,CAAC,EAAE,EACf,EAAE,MAAM,EAAE,MAAM,EAAE,CACnB,CAAC;YACF,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAExE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YACvC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YACxC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,sBAAsB,CAAC,EAAE,CAAC,CAAC;YAE/C,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC1D,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACvD,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAEvD,8DAA8D;YAC9D,gFAAgF;YAChF,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;YAC1D,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC5D,CAAC,EAAE,KAAK,CAAC,CAAC;IACZ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -1,5 +1,6 @@
1
- import { GqlChannelFactory, JobStatus, OperationEventTypes, ReactorBuilder, SyncBuilder, } from "@powerhousedao/reactor";
1
+ import { CompositeChannelFactory, ConsoleLogger, JobStatus, OperationEventTypes, ReactorBuilder, SyncBuilder, } from "@powerhousedao/reactor";
2
2
  import { driveDocumentModelModule } from "document-drive";
3
+ import { documentModelDocumentModelModule, } from "document-model";
3
4
  import { afterEach, beforeEach, describe, expect, it } from "vitest";
4
5
  import { createResolverBridge } from "./utils/gql-resolver-bridge.js";
5
6
  async function waitForJobCompletion(reactor, jobId, timeoutMs = 5000) {
@@ -52,16 +53,23 @@ async function waitForMultipleOperationsReady(eventBus, expectedCount, timeoutMs
52
53
  async function setupTwoReactorsWithGqlChannel() {
53
54
  const syncManagerRegistry = new Map();
54
55
  const resolverBridge = createResolverBridge(syncManagerRegistry);
55
- const gqlChannelFactoryA = new GqlChannelFactory();
56
- const gqlChannelFactoryB = new GqlChannelFactory();
56
+ const logger = new ConsoleLogger(["test"]);
57
+ const channelFactoryA = new CompositeChannelFactory(logger);
58
+ const channelFactoryB = new CompositeChannelFactory(logger);
59
+ const models = [
60
+ driveDocumentModelModule,
61
+ documentModelDocumentModelModule,
62
+ ];
57
63
  const reactorAModule = await new ReactorBuilder()
58
- .withSync(new SyncBuilder().withChannelFactory(gqlChannelFactoryA))
64
+ .withDocumentModels(models)
65
+ .withSync(new SyncBuilder().withChannelFactory(channelFactoryA))
59
66
  .buildModule();
60
67
  const reactorA = reactorAModule.reactor;
61
68
  const eventBusA = reactorAModule.eventBus;
62
69
  const syncManagerA = reactorAModule.syncModule.syncManager;
63
70
  const reactorBModule = await new ReactorBuilder()
64
- .withSync(new SyncBuilder().withChannelFactory(gqlChannelFactoryB))
71
+ .withDocumentModels(models)
72
+ .withSync(new SyncBuilder().withChannelFactory(channelFactoryB))
65
73
  .buildModule();
66
74
  const reactorB = reactorBModule.reactor;
67
75
  const eventBusB = reactorBModule.eventBus;
@@ -80,17 +88,9 @@ async function setupTwoReactorsWithGqlChannel() {
80
88
  retryBaseDelayMs: 50,
81
89
  fetchFn: resolverBridge,
82
90
  };
83
- const gqlParamsToA = {
84
- url: "http://reactorA/graphql",
85
- pollIntervalMs: 100,
86
- maxFailures: 10,
87
- retryBaseDelayMs: 50,
88
- fetchFn: resolverBridge,
89
- };
90
- const remoteAtoB = await syncManagerA.add("remoteB", "collection1", { type: "gql", parameters: gqlParamsToB }, filter);
91
- await syncManagerB.add("incoming-from-A", "collection1", { type: "gql", parameters: gqlParamsToA }, filter, {}, remoteAtoB.id);
92
- const remoteBtoA = await syncManagerB.add("remoteA", "collection1", { type: "gql", parameters: gqlParamsToA }, filter);
93
- await syncManagerA.add("incoming-from-B", "collection1", { type: "gql", parameters: gqlParamsToB }, filter, {}, remoteBtoA.id);
91
+ // ReactorA adds remote pointing to B
92
+ // touchChannel automatically creates receiving channel on B
93
+ await syncManagerA.add("remoteB", "collection1", { type: "gql", parameters: gqlParamsToB }, filter);
94
94
  return { reactorA, reactorB, eventBusA, eventBusB };
95
95
  }
96
96
  describe("Two-Reactor Sync with GqlChannel", () => {
@@ -156,21 +156,42 @@ describe("Two-Reactor Sync with GqlChannel", () => {
156
156
  expect(docA.document).toEqual(docB.document);
157
157
  });
158
158
  it("should sync multiple documents with concurrent operations from both reactors", async () => {
159
- const docA = driveDocumentModelModule.utils.createDocument();
160
- const docB = driveDocumentModelModule.utils.createDocument();
161
- const docC = driveDocumentModelModule.utils.createDocument();
162
- const docD = driveDocumentModelModule.utils.createDocument();
163
- const waitForCreatesA = waitForMultipleOperationsReady(eventBusA, 2, 15000);
164
- const waitForCreatesB = waitForMultipleOperationsReady(eventBusB, 2, 15000);
165
- void reactorA.create(docA);
166
- void reactorB.create(docC);
167
- void reactorA.create(docB);
168
- void reactorB.create(docD);
169
- await waitForCreatesA;
170
- await waitForCreatesB;
171
- const waitForMutatesA = waitForMultipleOperationsReady(eventBusA, 2, 15000);
172
- const waitForMutatesB = waitForMultipleOperationsReady(eventBusB, 2, 15000);
173
- void reactorA.execute(docA.header.id, "main", [
159
+ // Create 4 documents (2 for each reactor)
160
+ const docA1 = driveDocumentModelModule.utils.createDocument();
161
+ const docA2 = driveDocumentModelModule.utils.createDocument();
162
+ const docB1 = driveDocumentModelModule.utils.createDocument();
163
+ const docB2 = driveDocumentModelModule.utils.createDocument();
164
+ const allDocIds = [
165
+ docA1.header.id,
166
+ docA2.header.id,
167
+ docB1.header.id,
168
+ docB2.header.id,
169
+ ];
170
+ // Set up listeners for docs to sync to the other reactor
171
+ const readyOnB_A1 = waitForOperationsReady(eventBusB, docA1.header.id);
172
+ const readyOnB_A2 = waitForOperationsReady(eventBusB, docA2.header.id);
173
+ const readyOnA_B1 = waitForOperationsReady(eventBusA, docB1.header.id);
174
+ const readyOnA_B2 = waitForOperationsReady(eventBusA, docB2.header.id);
175
+ // Create documents on their respective reactors
176
+ const [jobA1, jobA2] = await Promise.all([
177
+ reactorA.create(docA1),
178
+ reactorA.create(docA2),
179
+ ]);
180
+ const [jobB1, jobB2] = await Promise.all([
181
+ reactorB.create(docB1),
182
+ reactorB.create(docB2),
183
+ ]);
184
+ // Wait for all creates to complete on source reactors
185
+ await Promise.all([
186
+ waitForJobCompletion(reactorA, jobA1.id),
187
+ waitForJobCompletion(reactorA, jobA2.id),
188
+ waitForJobCompletion(reactorB, jobB1.id),
189
+ waitForJobCompletion(reactorB, jobB2.id),
190
+ ]);
191
+ // Wait for all docs to sync to the other reactor
192
+ await Promise.all([readyOnB_A1, readyOnB_A2, readyOnA_B1, readyOnA_B2]);
193
+ // Now fire concurrent modify operations on each doc
194
+ void reactorA.execute(docA1.header.id, "main", [
174
195
  driveDocumentModelModule.actions.setDriveName({ name: "Drive A1" }),
175
196
  driveDocumentModelModule.actions.addFolder({
176
197
  id: "folder-a1",
@@ -178,53 +199,84 @@ describe("Two-Reactor Sync with GqlChannel", () => {
178
199
  parentFolder: null,
179
200
  }),
180
201
  ]);
181
- void reactorB.execute(docC.header.id, "main", [
182
- driveDocumentModelModule.actions.setDriveName({ name: "Drive C1" }),
202
+ void reactorA.execute(docA2.header.id, "main", [
203
+ driveDocumentModelModule.actions.setDriveName({ name: "Drive A2" }),
183
204
  driveDocumentModelModule.actions.addFolder({
184
- id: "folder-c1",
185
- name: "Folder C1",
205
+ id: "folder-a2",
206
+ name: "Folder A2",
186
207
  parentFolder: null,
187
208
  }),
188
209
  ]);
189
- void reactorA.execute(docB.header.id, "main", [
190
- driveDocumentModelModule.actions.setDriveIcon({ icon: "icon-b1" }),
210
+ void reactorB.execute(docB1.header.id, "main", [
211
+ driveDocumentModelModule.actions.setDriveName({ name: "Drive B1" }),
191
212
  driveDocumentModelModule.actions.addFolder({
192
213
  id: "folder-b1",
193
214
  name: "Folder B1",
194
215
  parentFolder: null,
195
216
  }),
196
217
  ]);
197
- void reactorB.execute(docD.header.id, "main", [
198
- driveDocumentModelModule.actions.setDriveName({ name: "Drive D1" }),
199
- driveDocumentModelModule.actions.updateNode({
200
- id: docD.header.id,
201
- name: "Updated D1",
218
+ void reactorB.execute(docB2.header.id, "main", [
219
+ driveDocumentModelModule.actions.setDriveName({ name: "Drive B2" }),
220
+ driveDocumentModelModule.actions.addFolder({
221
+ id: "folder-b2",
222
+ name: "Folder B2",
223
+ parentFolder: null,
202
224
  }),
203
225
  ]);
204
- await waitForMutatesA;
205
- await waitForMutatesB;
206
- const documents = [
207
- { id: docA.header.id, name: "docA" },
208
- { id: docB.header.id, name: "docB" },
209
- { id: docC.header.id, name: "docC" },
210
- { id: docD.header.id, name: "docD" },
211
- ];
212
- for (const doc of documents) {
213
- const resultA = await reactorA.getOperations(doc.id, {
214
- branch: "main",
215
- });
226
+ // Poll until all 4 documents are synced on both reactors
227
+ const startTime = Date.now();
228
+ const timeout = 25000;
229
+ let synced = false;
230
+ while (Date.now() - startTime < timeout) {
231
+ let allDocsSynced = true;
232
+ for (const docId of allDocIds) {
233
+ try {
234
+ const resultA = await reactorA.getOperations(docId, {
235
+ branch: "main",
236
+ });
237
+ const opsA = Object.values(resultA).flatMap((scope) => scope.results);
238
+ const resultB = await reactorB.getOperations(docId, {
239
+ branch: "main",
240
+ });
241
+ 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
+ if (opsA.length < 2 ||
245
+ opsB.length < 2 ||
246
+ opsA.length !== opsB.length) {
247
+ allDocsSynced = false;
248
+ break;
249
+ }
250
+ }
251
+ catch {
252
+ // Document may not exist on one reactor yet
253
+ allDocsSynced = false;
254
+ break;
255
+ }
256
+ }
257
+ if (allDocsSynced) {
258
+ synced = true;
259
+ break;
260
+ }
261
+ await new Promise((resolve) => setTimeout(resolve, 100));
262
+ }
263
+ expect(synced).toBe(true);
264
+ // Verify operation equality for all 4 documents
265
+ for (const docId of allDocIds) {
266
+ const resultA = await reactorA.getOperations(docId, { branch: "main" });
216
267
  const opsA = Object.values(resultA).flatMap((scope) => scope.results);
217
- const resultB = await reactorB.getOperations(doc.id, {
218
- branch: "main",
219
- });
268
+ const resultB = await reactorB.getOperations(docId, { branch: "main" });
220
269
  const opsB = Object.values(resultB).flatMap((scope) => scope.results);
221
270
  expect(opsA.length).toBeGreaterThan(0);
222
271
  expect(opsB.length).toBe(opsA.length);
223
272
  for (let i = 0; i < opsA.length; i++) {
224
273
  expect(opsB[i]).toEqual(opsA[i]);
225
274
  }
226
- const docFromA = await reactorA.get(doc.id, { branch: "main" });
227
- const docFromB = await reactorB.get(doc.id, { branch: "main" });
275
+ }
276
+ // Verify document state equality for all 4 documents
277
+ for (const docId of allDocIds) {
278
+ const docFromA = await reactorA.get(docId, { branch: "main" });
279
+ const docFromB = await reactorB.get(docId, { branch: "main" });
228
280
  expect(docFromA.document).toEqual(docFromB.document);
229
281
  }
230
282
  }, 30000);