jazz-run 0.14.27 → 0.15.0

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.
@@ -1,4 +1,4 @@
1
1
 
2
- > jazz-run@0.14.27 build /home/runner/_work/jazz/jazz/packages/jazz-run
2
+ > jazz-run@0.15.0 build /home/runner/_work/jazz/jazz/packages/jazz-run
3
3
  > rm -rf ./dist && tsc --sourceMap --outDir dist && chmod +x ./dist/index.js
4
4
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # jazz-run
2
2
 
3
+ ## 0.15.0
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [1378a1f]
8
+ - Updated dependencies [0fa051a]
9
+ - jazz-tools@0.15.0
10
+ - cojson@0.15.0
11
+ - cojson-storage-sqlite@0.15.0
12
+ - cojson-transport-ws@0.15.0
13
+
14
+ ## 0.14.28
15
+
16
+ ### Patch Changes
17
+
18
+ - Updated dependencies [06c5a1c]
19
+ - jazz-tools@0.14.28
20
+ - cojson@0.14.28
21
+ - cojson-storage-sqlite@0.14.28
22
+ - cojson-transport-ws@0.14.28
23
+
3
24
  ## 0.14.27
4
25
 
5
26
  ### Patch Changes
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=startSyncServer.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"startSyncServer.test.d.ts","sourceRoot":"","sources":["../../src/tests/startSyncServer.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,63 @@
1
+ import { randomUUID } from "crypto";
2
+ import { tmpdir } from "os";
3
+ import { join } from "path";
4
+ import { co, z } from "jazz-tools";
5
+ import { startWorker } from "jazz-tools/worker";
6
+ import { describe, expect, test } from "vitest";
7
+ import { createWorkerAccount } from "../createWorkerAccount.js";
8
+ import { startSyncServer } from "../startSyncServer.js";
9
+ const TestMap = co.map({
10
+ value: z.string(),
11
+ });
12
+ describe("startSyncServer", () => {
13
+ test("persists values in storage and loads them after restart", async () => {
14
+ // Create a temporary database file
15
+ const dbPath = join(tmpdir(), `test-${randomUUID()}.db`);
16
+ // Start first server instance
17
+ const server1 = await startSyncServer({
18
+ port: "0", // Random available port
19
+ inMemory: false,
20
+ db: dbPath,
21
+ });
22
+ const port = server1.address().port;
23
+ const syncServer = `ws://localhost:${port}`;
24
+ // Create worker account and start first worker
25
+ const { accountID, agentSecret } = await createWorkerAccount({
26
+ name: "test-worker",
27
+ peer: syncServer,
28
+ });
29
+ const worker1 = await startWorker({
30
+ accountID,
31
+ accountSecret: agentSecret,
32
+ syncServer,
33
+ });
34
+ // Create and sync test data
35
+ const map = TestMap.create({ value: "testValue" });
36
+ // Close first server
37
+ await worker1.done();
38
+ server1.close();
39
+ // Start second server instance with same DB
40
+ const server2 = await startSyncServer({
41
+ port: "0",
42
+ inMemory: false,
43
+ db: dbPath,
44
+ });
45
+ const port2 = server2.address().port;
46
+ const syncServer2 = `ws://localhost:${port2}`;
47
+ // Start second worker with same account
48
+ const worker2 = await startWorker({
49
+ accountID,
50
+ accountSecret: agentSecret,
51
+ syncServer: syncServer2,
52
+ });
53
+ // Try to load the previously created map
54
+ const loadedMap = await TestMap.load(map.id, {});
55
+ // Verify the data persisted
56
+ expect(loadedMap).not.toBe(null);
57
+ expect(loadedMap?.value).toBe("testValue");
58
+ // Cleanup
59
+ await worker2.done();
60
+ server2.close();
61
+ });
62
+ });
63
+ //# sourceMappingURL=startSyncServer.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"startSyncServer.test.js","sourceRoot":"","sources":["../../src/tests/startSyncServer.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC;AAC5B,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,EAAE,EAAE,CAAC,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,MAAM,OAAO,GAAG,EAAE,CAAC,GAAG,CAAC;IACrB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;CAClB,CAAC,CAAC;AAEH,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,IAAI,CAAC,yDAAyD,EAAE,KAAK,IAAI,EAAE;QACzE,mCAAmC;QACnC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,EAAE,QAAQ,UAAU,EAAE,KAAK,CAAC,CAAC;QAEzD,8BAA8B;QAC9B,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC;YACpC,IAAI,EAAE,GAAG,EAAE,wBAAwB;YACnC,QAAQ,EAAE,KAAK;YACf,EAAE,EAAE,MAAM;SACX,CAAC,CAAC;QAEH,MAAM,IAAI,GAAI,OAAO,CAAC,OAAO,EAAU,CAAC,IAAI,CAAC;QAC7C,MAAM,UAAU,GAAG,kBAAkB,IAAI,EAAE,CAAC;QAE5C,+CAA+C;QAC/C,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,GAAG,MAAM,mBAAmB,CAAC;YAC3D,IAAI,EAAE,aAAa;YACnB,IAAI,EAAE,UAAU;SACjB,CAAC,CAAC;QAEH,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC;YAChC,SAAS;YACT,aAAa,EAAE,WAAW;YAC1B,UAAU;SACX,CAAC,CAAC;QAEH,4BAA4B;QAC5B,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;QAEnD,qBAAqB;QACrB,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC;QACrB,OAAO,CAAC,KAAK,EAAE,CAAC;QAEhB,4CAA4C;QAC5C,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC;YACpC,IAAI,EAAE,GAAG;YACT,QAAQ,EAAE,KAAK;YACf,EAAE,EAAE,MAAM;SACX,CAAC,CAAC;QAEH,MAAM,KAAK,GAAI,OAAO,CAAC,OAAO,EAAU,CAAC,IAAI,CAAC;QAC9C,MAAM,WAAW,GAAG,kBAAkB,KAAK,EAAE,CAAC;QAE9C,wCAAwC;QACxC,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC;YAChC,SAAS;YACT,aAAa,EAAE,WAAW;YAC1B,UAAU,EAAE,WAAW;SACxB,CAAC,CAAC;QAEH,yCAAyC;QACzC,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QAEjD,4BAA4B;QAC5B,MAAM,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjC,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAE3C,UAAU;QACV,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC;QACrB,OAAO,CAAC,KAAK,EAAE,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=startWorker.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"startWorker.test.d.ts","sourceRoot":"","sources":["../../src/tests/startWorker.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,229 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { unlinkSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { Group, InboxSender, co, z, } from "jazz-tools";
6
+ import { startWorker } from "jazz-tools/worker";
7
+ import { afterAll, describe, expect, onTestFinished, test } from "vitest";
8
+ import { createWorkerAccount } from "../createWorkerAccount.js";
9
+ import { startSyncServer } from "../startSyncServer.js";
10
+ import { waitFor } from "./utils.js";
11
+ const dbPath = join(tmpdir(), `test-${randomUUID()}.db`);
12
+ afterAll(() => {
13
+ unlinkSync(dbPath);
14
+ });
15
+ async function setup(AccountSchema) {
16
+ const { server, port } = await setupSyncServer();
17
+ const syncServer = `ws://localhost:${port}`;
18
+ const { worker, done, waitForConnection, subscribeToConnectionChange } = await setupWorker(syncServer, AccountSchema);
19
+ return {
20
+ worker,
21
+ done,
22
+ syncServer,
23
+ server,
24
+ port,
25
+ waitForConnection,
26
+ subscribeToConnectionChange,
27
+ };
28
+ }
29
+ async function setupSyncServer(defaultPort = "0") {
30
+ const server = await startSyncServer({
31
+ port: defaultPort,
32
+ inMemory: false,
33
+ db: dbPath,
34
+ });
35
+ const port = server.address().port.toString();
36
+ onTestFinished(() => {
37
+ server.close();
38
+ });
39
+ return { server, port };
40
+ }
41
+ async function setupWorker(syncServer, AccountSchema) {
42
+ const { accountID, agentSecret } = await createWorkerAccount({
43
+ name: "test-worker",
44
+ peer: syncServer,
45
+ });
46
+ return startWorker({
47
+ accountID: accountID,
48
+ accountSecret: agentSecret,
49
+ syncServer,
50
+ AccountSchema,
51
+ });
52
+ }
53
+ const TestMap = co.map({
54
+ value: z.string(),
55
+ });
56
+ describe("startWorker integration", () => {
57
+ test("worker connects to sync server successfully", async () => {
58
+ const worker1 = await setup();
59
+ const worker2 = await setupWorker(worker1.syncServer);
60
+ const group = Group.create({ owner: worker1.worker });
61
+ group.addMember("everyone", "reader");
62
+ const map = TestMap.create({
63
+ value: "test",
64
+ }, { owner: group });
65
+ await map.waitForSync();
66
+ const mapOnWorker2 = await TestMap.load(map.id, { loadAs: worker2.worker });
67
+ expect(mapOnWorker2?.value).toBe("test");
68
+ await worker1.done();
69
+ await worker2.done();
70
+ });
71
+ test("worker handles successfully the custom account migration", async () => {
72
+ const AccountRoot = co.map({
73
+ value: z.string(),
74
+ });
75
+ let shouldReloadPreviousAccount = false;
76
+ const CustomAccount = co
77
+ .account({
78
+ root: AccountRoot,
79
+ profile: co.profile(),
80
+ })
81
+ .withMigration((account) => {
82
+ if (account.root === undefined) {
83
+ if (shouldReloadPreviousAccount) {
84
+ throw new Error("Previous account not found");
85
+ }
86
+ shouldReloadPreviousAccount = true;
87
+ account.root = AccountRoot.create({
88
+ value: "test",
89
+ }, account);
90
+ }
91
+ });
92
+ const worker1 = await setup(CustomAccount);
93
+ const { root } = await worker1.worker.ensureLoaded({
94
+ resolve: { root: true },
95
+ });
96
+ expect(root.value).toBe("test");
97
+ await worker1.done();
98
+ const worker2 = await startWorker({
99
+ accountID: worker1.worker.id,
100
+ accountSecret: worker1.worker._raw.core.node.getCurrentAgent().agentSecret,
101
+ syncServer: worker1.syncServer,
102
+ AccountSchema: CustomAccount,
103
+ });
104
+ const { root: root2 } = await worker2.worker.ensureLoaded({
105
+ resolve: { root: true },
106
+ });
107
+ expect(root2.value).toBe("test");
108
+ await worker2.done();
109
+ });
110
+ test("waits for all coValues to sync before resolving done", async () => {
111
+ const worker1 = await setup();
112
+ const group = Group.create({ owner: worker1.worker });
113
+ group.addMember("everyone", "reader");
114
+ const map = TestMap.create({
115
+ value: "test",
116
+ }, { owner: group });
117
+ await worker1.done();
118
+ const worker2 = await setupWorker(worker1.syncServer);
119
+ const mapOnWorker2 = await TestMap.load(map.id, { loadAs: worker2.worker });
120
+ expect(mapOnWorker2?.value).toBe("test");
121
+ await worker2.done();
122
+ });
123
+ test("reiceves the messages from the inbox", async () => {
124
+ const worker1 = await setup();
125
+ const worker2 = await setupWorker(worker1.syncServer);
126
+ const group = Group.create({ owner: worker1.worker });
127
+ const map = TestMap.create({
128
+ value: "Hello!",
129
+ }, { owner: group });
130
+ worker2.experimental.inbox.subscribe(TestMap, async (value) => {
131
+ return TestMap.create({
132
+ value: value.value + " Responded from the inbox",
133
+ }, { owner: value._owner });
134
+ });
135
+ const sender = await InboxSender.load(worker2.worker.id, worker1.worker);
136
+ const resultId = await sender.sendMessage(map);
137
+ const result = await TestMap.load(resultId, { loadAs: worker2.worker });
138
+ expect(result?.value).toEqual("Hello! Responded from the inbox");
139
+ await worker1.done();
140
+ await worker2.done();
141
+ });
142
+ // Flaky test, fails randomly on CI
143
+ test.skip("worker reconnects when sync server is closed and reopened", async () => {
144
+ const worker1 = await setup();
145
+ const worker2 = await setupWorker(worker1.syncServer);
146
+ const group = Group.create({ owner: worker1.worker });
147
+ group.addMember("everyone", "reader");
148
+ const map = TestMap.create({
149
+ value: "initial value",
150
+ }, { owner: group });
151
+ await map.waitForSync();
152
+ // Close the sync server
153
+ worker1.server.close();
154
+ // Create a new value while server is down
155
+ const map2 = TestMap.create({
156
+ value: "created while offline",
157
+ }, { owner: group });
158
+ map.value = "updated while offline";
159
+ // Start a new sync server on the same port
160
+ const newServer = await startSyncServer({
161
+ port: worker1.port,
162
+ inMemory: true,
163
+ db: "",
164
+ });
165
+ // Wait for reconnection
166
+ await worker1.waitForConnection();
167
+ await worker2.waitForConnection();
168
+ await worker1.worker.waitForAllCoValuesSync();
169
+ // Verify both old and new values are synced
170
+ const mapOnWorker2 = await TestMap.load(map.id, { loadAs: worker2.worker });
171
+ const map2OnWorker2 = await TestMap.load(map2.id, {
172
+ loadAs: worker2.worker,
173
+ });
174
+ expect(mapOnWorker2?.value).toBe("updated while offline");
175
+ expect(map2OnWorker2?.value).toBe("created while offline");
176
+ // Cleanup
177
+ await worker2.done();
178
+ newServer.close();
179
+ });
180
+ test("waitForConnection resolves when connection is established", async () => {
181
+ const worker1 = await setup();
182
+ // Initially should be connected
183
+ await worker1.waitForConnection();
184
+ // Close the sync server
185
+ worker1.server.close();
186
+ // Start a new sync server on the same port
187
+ const newServer = await startSyncServer({
188
+ port: worker1.port,
189
+ inMemory: true,
190
+ db: "",
191
+ });
192
+ // Should reconnect and resolve
193
+ await worker1.waitForConnection();
194
+ // Cleanup
195
+ await worker1.done();
196
+ newServer.close();
197
+ });
198
+ test("subscribeToConnectionChange notifies on connection state changes", async () => {
199
+ const worker1 = await setup();
200
+ const connectionStates = [];
201
+ // Subscribe to connection changes
202
+ const unsubscribe = worker1.subscribeToConnectionChange((isConnected) => {
203
+ connectionStates.push(isConnected);
204
+ });
205
+ await waitFor(() => {
206
+ expect(connectionStates).toEqual([true]);
207
+ });
208
+ // Close the sync server
209
+ worker1.server.close();
210
+ await waitFor(() => {
211
+ expect(connectionStates).toEqual([true, false]);
212
+ });
213
+ // Start a new sync server on the same port
214
+ const newServer = await startSyncServer({
215
+ port: worker1.port,
216
+ inMemory: true,
217
+ db: "",
218
+ });
219
+ // Wait a bit for the reconnection to be detected
220
+ await waitFor(() => {
221
+ expect(connectionStates).toEqual([true, false, true]);
222
+ });
223
+ // Cleanup
224
+ unsubscribe();
225
+ await worker1.done();
226
+ newServer.close();
227
+ });
228
+ });
229
+ //# sourceMappingURL=startWorker.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"startWorker.test.js","sourceRoot":"","sources":["../../src/tests/startWorker.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function waitFor(callback: () => boolean | void): Promise<void>;
2
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/tests/utils.ts"],"names":[],"mappings":"AAAA,wBAAgB,OAAO,CAAC,QAAQ,EAAE,MAAM,OAAO,GAAG,IAAI,iBA0BrD"}
@@ -0,0 +1,25 @@
1
+ export function waitFor(callback) {
2
+ return new Promise((resolve, reject) => {
3
+ const checkPassed = () => {
4
+ try {
5
+ return { ok: callback(), error: null };
6
+ }
7
+ catch (error) {
8
+ return { ok: false, error };
9
+ }
10
+ };
11
+ let retries = 0;
12
+ const interval = setInterval(() => {
13
+ const { ok, error } = checkPassed();
14
+ if (ok !== false) {
15
+ clearInterval(interval);
16
+ resolve();
17
+ }
18
+ if (++retries > 10) {
19
+ clearInterval(interval);
20
+ reject(error);
21
+ }
22
+ }, 100);
23
+ });
24
+ }
25
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/tests/utils.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,OAAO,CAAC,QAA8B;IACpD,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC3C,MAAM,WAAW,GAAG,GAAG,EAAE;YACvB,IAAI,CAAC;gBACH,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;YACzC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;YAC9B,CAAC;QACH,CAAC,CAAC;QAEF,IAAI,OAAO,GAAG,CAAC,CAAC;QAEhB,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE;YAChC,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,WAAW,EAAE,CAAC;YAEpC,IAAI,EAAE,KAAK,KAAK,EAAE,CAAC;gBACjB,aAAa,CAAC,QAAQ,CAAC,CAAC;gBACxB,OAAO,EAAE,CAAC;YACZ,CAAC;YAED,IAAI,EAAE,OAAO,GAAG,EAAE,EAAE,CAAC;gBACnB,aAAa,CAAC,QAAQ,CAAC,CAAC;gBACxB,MAAM,CAAC,KAAK,CAAC,CAAC;YAChB,CAAC;QACH,CAAC,EAAE,GAAG,CAAC,CAAC;IACV,CAAC,CAAC,CAAC;AACL,CAAC"}
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "bin": "./dist/index.js",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
- "version": "0.14.27",
6
+ "version": "0.15.0",
7
7
  "exports": {
8
8
  "./startSyncServer": {
9
9
  "import": "./dist/startSyncServer.js",
@@ -21,11 +21,11 @@
21
21
  "@effect/printer-ansi": "^0.34.5",
22
22
  "@effect/schema": "^0.71.1",
23
23
  "@effect/typeclass": "^0.25.5",
24
- "cojson": "0.14.27",
25
- "cojson-storage-sqlite": "0.14.27",
26
- "cojson-transport-ws": "0.14.27",
24
+ "cojson": "0.15.0",
25
+ "cojson-storage-sqlite": "0.15.0",
26
+ "cojson-transport-ws": "0.15.0",
27
27
  "effect": "^3.6.5",
28
- "jazz-tools": "0.14.27",
28
+ "jazz-tools": "0.15.0",
29
29
  "ws": "^8.14.2"
30
30
  },
31
31
  "devDependencies": {
@@ -0,0 +1,76 @@
1
+ import { randomUUID } from "crypto";
2
+ import { tmpdir } from "os";
3
+ import { join } from "path";
4
+ import { co, z } from "jazz-tools";
5
+ import { startWorker } from "jazz-tools/worker";
6
+ import { describe, expect, test } from "vitest";
7
+ import { createWorkerAccount } from "../createWorkerAccount.js";
8
+ import { startSyncServer } from "../startSyncServer.js";
9
+
10
+ const TestMap = co.map({
11
+ value: z.string(),
12
+ });
13
+
14
+ describe("startSyncServer", () => {
15
+ test("persists values in storage and loads them after restart", async () => {
16
+ // Create a temporary database file
17
+ const dbPath = join(tmpdir(), `test-${randomUUID()}.db`);
18
+
19
+ // Start first server instance
20
+ const server1 = await startSyncServer({
21
+ port: "0", // Random available port
22
+ inMemory: false,
23
+ db: dbPath,
24
+ });
25
+
26
+ const port = (server1.address() as any).port;
27
+ const syncServer = `ws://localhost:${port}`;
28
+
29
+ // Create worker account and start first worker
30
+ const { accountID, agentSecret } = await createWorkerAccount({
31
+ name: "test-worker",
32
+ peer: syncServer,
33
+ });
34
+
35
+ const worker1 = await startWorker({
36
+ accountID,
37
+ accountSecret: agentSecret,
38
+ syncServer,
39
+ });
40
+
41
+ // Create and sync test data
42
+ const map = TestMap.create({ value: "testValue" });
43
+
44
+ // Close first server
45
+ await worker1.done();
46
+ server1.close();
47
+
48
+ // Start second server instance with same DB
49
+ const server2 = await startSyncServer({
50
+ port: "0",
51
+ inMemory: false,
52
+ db: dbPath,
53
+ });
54
+
55
+ const port2 = (server2.address() as any).port;
56
+ const syncServer2 = `ws://localhost:${port2}`;
57
+
58
+ // Start second worker with same account
59
+ const worker2 = await startWorker({
60
+ accountID,
61
+ accountSecret: agentSecret,
62
+ syncServer: syncServer2,
63
+ });
64
+
65
+ // Try to load the previously created map
66
+ const loadedMap = await TestMap.load(map.id, {});
67
+
68
+ // Verify the data persisted
69
+ expect(loadedMap).not.toBe(null);
70
+ expect(loadedMap?.value).toBe("testValue");
71
+
72
+ // Cleanup
73
+ await worker2.done();
74
+ server2.close();
75
+ });
76
+ });
@@ -0,0 +1,352 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { unlinkSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import {
6
+ Account,
7
+ AccountClass,
8
+ AccountSchema,
9
+ AnyAccountSchema,
10
+ CoMap,
11
+ CoValueFromRaw,
12
+ Group,
13
+ InboxSender,
14
+ Loaded,
15
+ co,
16
+ coField,
17
+ z,
18
+ } from "jazz-tools";
19
+ import { startWorker } from "jazz-tools/worker";
20
+ import { afterAll, describe, expect, onTestFinished, test } from "vitest";
21
+ import { createWorkerAccount } from "../createWorkerAccount.js";
22
+ import { startSyncServer } from "../startSyncServer.js";
23
+ import { waitFor } from "./utils.js";
24
+
25
+ const dbPath = join(tmpdir(), `test-${randomUUID()}.db`);
26
+
27
+ afterAll(() => {
28
+ unlinkSync(dbPath);
29
+ });
30
+
31
+ async function setup<
32
+ S extends
33
+ | (AccountClass<Account> & CoValueFromRaw<Account>)
34
+ | AnyAccountSchema,
35
+ >(AccountSchema?: S) {
36
+ const { server, port } = await setupSyncServer();
37
+
38
+ const syncServer = `ws://localhost:${port}`;
39
+
40
+ const { worker, done, waitForConnection, subscribeToConnectionChange } =
41
+ await setupWorker(syncServer, AccountSchema);
42
+
43
+ return {
44
+ worker,
45
+ done,
46
+ syncServer,
47
+ server,
48
+ port,
49
+ waitForConnection,
50
+ subscribeToConnectionChange,
51
+ };
52
+ }
53
+
54
+ async function setupSyncServer(defaultPort = "0") {
55
+ const server = await startSyncServer({
56
+ port: defaultPort,
57
+ inMemory: false,
58
+ db: dbPath,
59
+ });
60
+
61
+ const port = (server.address() as { port: number }).port.toString();
62
+
63
+ onTestFinished(() => {
64
+ server.close();
65
+ });
66
+
67
+ return { server, port };
68
+ }
69
+
70
+ async function setupWorker<
71
+ S extends
72
+ | (AccountClass<Account> & CoValueFromRaw<Account>)
73
+ | AnyAccountSchema,
74
+ >(syncServer: string, AccountSchema?: S) {
75
+ const { accountID, agentSecret } = await createWorkerAccount({
76
+ name: "test-worker",
77
+ peer: syncServer,
78
+ });
79
+
80
+ return startWorker({
81
+ accountID: accountID,
82
+ accountSecret: agentSecret,
83
+ syncServer,
84
+ AccountSchema,
85
+ });
86
+ }
87
+
88
+ const TestMap = co.map({
89
+ value: z.string(),
90
+ });
91
+
92
+ describe("startWorker integration", () => {
93
+ test("worker connects to sync server successfully", async () => {
94
+ const worker1 = await setup();
95
+ const worker2 = await setupWorker(worker1.syncServer);
96
+
97
+ const group = Group.create({ owner: worker1.worker });
98
+ group.addMember("everyone", "reader");
99
+
100
+ const map = TestMap.create(
101
+ {
102
+ value: "test",
103
+ },
104
+ { owner: group },
105
+ );
106
+
107
+ await map.waitForSync();
108
+
109
+ const mapOnWorker2 = await TestMap.load(map.id, { loadAs: worker2.worker });
110
+
111
+ expect(mapOnWorker2?.value).toBe("test");
112
+
113
+ await worker1.done();
114
+ await worker2.done();
115
+ });
116
+
117
+ test("worker handles successfully the custom account migration", async () => {
118
+ const AccountRoot = co.map({
119
+ value: z.string(),
120
+ });
121
+
122
+ let shouldReloadPreviousAccount = false;
123
+
124
+ const CustomAccount = co
125
+ .account({
126
+ root: AccountRoot,
127
+ profile: co.profile(),
128
+ })
129
+ .withMigration((account) => {
130
+ if (account.root === undefined) {
131
+ if (shouldReloadPreviousAccount) {
132
+ throw new Error("Previous account not found");
133
+ }
134
+
135
+ shouldReloadPreviousAccount = true;
136
+
137
+ account.root = AccountRoot.create(
138
+ {
139
+ value: "test",
140
+ },
141
+ account,
142
+ );
143
+ }
144
+ });
145
+
146
+ const worker1 = await setup(CustomAccount);
147
+
148
+ const { root } = await worker1.worker.ensureLoaded({
149
+ resolve: { root: true },
150
+ });
151
+
152
+ expect(root.value).toBe("test");
153
+
154
+ await worker1.done();
155
+
156
+ const worker2 = await startWorker({
157
+ accountID: worker1.worker.id,
158
+ accountSecret:
159
+ worker1.worker._raw.core.node.getCurrentAgent().agentSecret,
160
+ syncServer: worker1.syncServer,
161
+ AccountSchema: CustomAccount,
162
+ });
163
+
164
+ const { root: root2 } = await worker2.worker.ensureLoaded({
165
+ resolve: { root: true },
166
+ });
167
+
168
+ expect(root2.value).toBe("test");
169
+
170
+ await worker2.done();
171
+ });
172
+
173
+ test("waits for all coValues to sync before resolving done", async () => {
174
+ const worker1 = await setup();
175
+
176
+ const group = Group.create({ owner: worker1.worker });
177
+ group.addMember("everyone", "reader");
178
+
179
+ const map = TestMap.create(
180
+ {
181
+ value: "test",
182
+ },
183
+ { owner: group },
184
+ );
185
+
186
+ await worker1.done();
187
+
188
+ const worker2 = await setupWorker(worker1.syncServer);
189
+
190
+ const mapOnWorker2 = await TestMap.load(map.id, { loadAs: worker2.worker });
191
+
192
+ expect(mapOnWorker2?.value).toBe("test");
193
+
194
+ await worker2.done();
195
+ });
196
+
197
+ test("reiceves the messages from the inbox", async () => {
198
+ const worker1 = await setup();
199
+ const worker2 = await setupWorker(worker1.syncServer);
200
+
201
+ const group = Group.create({ owner: worker1.worker });
202
+ const map = TestMap.create(
203
+ {
204
+ value: "Hello!",
205
+ },
206
+ { owner: group },
207
+ );
208
+
209
+ worker2.experimental.inbox.subscribe(TestMap, async (value) => {
210
+ return TestMap.create(
211
+ {
212
+ value: value.value + " Responded from the inbox",
213
+ },
214
+ { owner: value._owner },
215
+ );
216
+ });
217
+
218
+ const sender = await InboxSender.load<
219
+ Loaded<typeof TestMap>,
220
+ Loaded<typeof TestMap>
221
+ >(worker2.worker.id, worker1.worker);
222
+
223
+ const resultId = await sender.sendMessage(map);
224
+
225
+ const result = await TestMap.load(resultId, { loadAs: worker2.worker });
226
+
227
+ expect(result?.value).toEqual("Hello! Responded from the inbox");
228
+
229
+ await worker1.done();
230
+ await worker2.done();
231
+ });
232
+
233
+ // Flaky test, fails randomly on CI
234
+ test.skip("worker reconnects when sync server is closed and reopened", async () => {
235
+ const worker1 = await setup();
236
+ const worker2 = await setupWorker(worker1.syncServer);
237
+
238
+ const group = Group.create({ owner: worker1.worker });
239
+ group.addMember("everyone", "reader");
240
+
241
+ const map = TestMap.create(
242
+ {
243
+ value: "initial value",
244
+ },
245
+ { owner: group },
246
+ );
247
+
248
+ await map.waitForSync();
249
+
250
+ // Close the sync server
251
+ worker1.server.close();
252
+
253
+ // Create a new value while server is down
254
+ const map2 = TestMap.create(
255
+ {
256
+ value: "created while offline",
257
+ },
258
+ { owner: group },
259
+ );
260
+
261
+ map.value = "updated while offline";
262
+
263
+ // Start a new sync server on the same port
264
+ const newServer = await startSyncServer({
265
+ port: worker1.port,
266
+ inMemory: true,
267
+ db: "",
268
+ });
269
+
270
+ // Wait for reconnection
271
+ await worker1.waitForConnection();
272
+ await worker2.waitForConnection();
273
+
274
+ await worker1.worker.waitForAllCoValuesSync();
275
+
276
+ // Verify both old and new values are synced
277
+ const mapOnWorker2 = await TestMap.load(map.id, { loadAs: worker2.worker });
278
+ const map2OnWorker2 = await TestMap.load(map2.id, {
279
+ loadAs: worker2.worker,
280
+ });
281
+
282
+ expect(mapOnWorker2?.value).toBe("updated while offline");
283
+ expect(map2OnWorker2?.value).toBe("created while offline");
284
+
285
+ // Cleanup
286
+ await worker2.done();
287
+ newServer.close();
288
+ });
289
+
290
+ test("waitForConnection resolves when connection is established", async () => {
291
+ const worker1 = await setup();
292
+
293
+ // Initially should be connected
294
+ await worker1.waitForConnection();
295
+
296
+ // Close the sync server
297
+ worker1.server.close();
298
+
299
+ // Start a new sync server on the same port
300
+ const newServer = await startSyncServer({
301
+ port: worker1.port,
302
+ inMemory: true,
303
+ db: "",
304
+ });
305
+
306
+ // Should reconnect and resolve
307
+ await worker1.waitForConnection();
308
+
309
+ // Cleanup
310
+ await worker1.done();
311
+ newServer.close();
312
+ });
313
+
314
+ test("subscribeToConnectionChange notifies on connection state changes", async () => {
315
+ const worker1 = await setup();
316
+
317
+ const connectionStates: boolean[] = [];
318
+
319
+ // Subscribe to connection changes
320
+ const unsubscribe = worker1.subscribeToConnectionChange((isConnected) => {
321
+ connectionStates.push(isConnected);
322
+ });
323
+
324
+ await waitFor(() => {
325
+ expect(connectionStates).toEqual([true]);
326
+ });
327
+
328
+ // Close the sync server
329
+ worker1.server.close();
330
+
331
+ await waitFor(() => {
332
+ expect(connectionStates).toEqual([true, false]);
333
+ });
334
+
335
+ // Start a new sync server on the same port
336
+ const newServer = await startSyncServer({
337
+ port: worker1.port,
338
+ inMemory: true,
339
+ db: "",
340
+ });
341
+
342
+ // Wait a bit for the reconnection to be detected
343
+ await waitFor(() => {
344
+ expect(connectionStates).toEqual([true, false, true]);
345
+ });
346
+
347
+ // Cleanup
348
+ unsubscribe();
349
+ await worker1.done();
350
+ newServer.close();
351
+ });
352
+ });
@@ -0,0 +1,27 @@
1
+ export function waitFor(callback: () => boolean | void) {
2
+ return new Promise<void>((resolve, reject) => {
3
+ const checkPassed = () => {
4
+ try {
5
+ return { ok: callback(), error: null };
6
+ } catch (error) {
7
+ return { ok: false, error };
8
+ }
9
+ };
10
+
11
+ let retries = 0;
12
+
13
+ const interval = setInterval(() => {
14
+ const { ok, error } = checkPassed();
15
+
16
+ if (ok !== false) {
17
+ clearInterval(interval);
18
+ resolve();
19
+ }
20
+
21
+ if (++retries > 10) {
22
+ clearInterval(interval);
23
+ reject(error);
24
+ }
25
+ }, 100);
26
+ });
27
+ }