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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +21 -0
- package/dist/tests/startSyncServer.test.d.ts +2 -0
- package/dist/tests/startSyncServer.test.d.ts.map +1 -0
- package/dist/tests/startSyncServer.test.js +63 -0
- package/dist/tests/startSyncServer.test.js.map +1 -0
- package/dist/tests/startWorker.test.d.ts +2 -0
- package/dist/tests/startWorker.test.d.ts.map +1 -0
- package/dist/tests/startWorker.test.js +229 -0
- package/dist/tests/startWorker.test.js.map +1 -0
- package/dist/tests/utils.d.ts +2 -0
- package/dist/tests/utils.d.ts.map +1 -0
- package/dist/tests/utils.js +25 -0
- package/dist/tests/utils.js.map +1 -0
- package/package.json +5 -5
- package/src/tests/startSyncServer.test.ts +76 -0
- package/src/tests/startWorker.test.ts +352 -0
- package/src/tests/utils.ts +27 -0
package/.turbo/turbo-build.log
CHANGED
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 @@
|
|
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 @@
|
|
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 @@
|
|
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.
|
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.
|
25
|
-
"cojson-storage-sqlite": "0.
|
26
|
-
"cojson-transport-ws": "0.
|
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.
|
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
|
+
}
|