jazz-tools 0.18.5 → 0.18.7
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 +57 -57
- package/CHANGELOG.md +33 -0
- package/dist/better-auth/auth/client.d.ts.map +1 -1
- package/dist/better-auth/auth/client.js +7 -1
- package/dist/better-auth/auth/client.js.map +1 -1
- package/dist/better-auth/auth/react.d.ts +0 -2145
- package/dist/better-auth/auth/react.d.ts.map +1 -1
- package/dist/better-auth/auth/react.js +2 -14
- package/dist/better-auth/auth/react.js.map +1 -1
- package/dist/better-auth/auth/server.d.ts.map +1 -1
- package/dist/better-auth/auth/server.js +77 -22
- package/dist/better-auth/auth/server.js.map +1 -1
- package/dist/better-auth/auth/tests/react.test.d.ts +2 -0
- package/dist/better-auth/auth/tests/react.test.d.ts.map +1 -0
- package/dist/{chunk-3LE7N6TH.js → chunk-CFAY3FMQ.js} +192 -101
- package/dist/chunk-CFAY3FMQ.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/inspector/{custom-element-WCY6D3QJ.js → custom-element-G6SPZEBR.js} +308 -97
- package/dist/inspector/custom-element-G6SPZEBR.js.map +1 -0
- package/dist/inspector/index.d.ts +5 -1
- package/dist/inspector/index.d.ts.map +1 -1
- package/dist/inspector/index.js +318 -56
- package/dist/inspector/index.js.map +1 -1
- package/dist/inspector/register-custom-element.js +1 -1
- package/dist/inspector/ui/button.d.ts +1 -1
- package/dist/inspector/ui/button.d.ts.map +1 -1
- package/dist/inspector/ui/heading.d.ts +2 -1
- package/dist/inspector/ui/heading.d.ts.map +1 -1
- package/dist/inspector/ui/input.d.ts.map +1 -1
- package/dist/inspector/ui/modal.d.ts +16 -0
- package/dist/inspector/ui/modal.d.ts.map +1 -0
- package/dist/inspector/viewer/delete-local-data.d.ts +2 -0
- package/dist/inspector/viewer/delete-local-data.d.ts.map +1 -0
- package/dist/inspector/viewer/{inpsector-button.d.ts → inspector-button.d.ts} +1 -1
- package/dist/inspector/viewer/{inpsector-button.d.ts.map → inspector-button.d.ts.map} +1 -1
- package/dist/inspector/viewer/new-app.d.ts +1 -4
- package/dist/inspector/viewer/new-app.d.ts.map +1 -1
- package/dist/react/hooks.d.ts +1 -1
- package/dist/react/hooks.d.ts.map +1 -1
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +3 -1
- package/dist/react/index.js.map +1 -1
- package/dist/react-core/hooks.d.ts +133 -0
- package/dist/react-core/hooks.d.ts.map +1 -1
- package/dist/react-core/index.js +83 -17
- package/dist/react-core/index.js.map +1 -1
- package/dist/react-core/tests/useCoStateWithSelector.test.d.ts +2 -0
- package/dist/react-core/tests/useCoStateWithSelector.test.d.ts.map +1 -0
- package/dist/react-native-core/hooks.d.ts +1 -1
- package/dist/react-native-core/hooks.d.ts.map +1 -1
- package/dist/react-native-core/index.js +3 -1
- package/dist/react-native-core/index.js.map +1 -1
- package/dist/testing.js +2 -2
- package/dist/testing.js.map +1 -1
- package/dist/tools/coValues/CoValueBase.d.ts +14 -0
- package/dist/tools/coValues/CoValueBase.d.ts.map +1 -1
- package/dist/tools/coValues/coMap.d.ts +0 -12
- package/dist/tools/coValues/coMap.d.ts.map +1 -1
- package/dist/tools/coValues/inbox.d.ts +5 -5
- package/dist/tools/coValues/inbox.d.ts.map +1 -1
- package/dist/tools/implementation/createContext.d.ts +2 -1
- package/dist/tools/implementation/createContext.d.ts.map +1 -1
- package/dist/tools/tests/utils.d.ts.map +1 -1
- package/dist/worker/index.d.ts +12 -2
- package/dist/worker/index.d.ts.map +1 -1
- package/dist/worker/index.js +10 -4
- package/dist/worker/index.js.map +1 -1
- package/package.json +6 -4
- package/src/better-auth/auth/client.ts +8 -2
- package/src/better-auth/auth/react.tsx +2 -51
- package/src/better-auth/auth/server.ts +98 -24
- package/src/better-auth/auth/tests/client.test.ts +92 -4
- package/src/better-auth/auth/tests/react.test.tsx +43 -0
- package/src/better-auth/auth/tests/server.test.ts +276 -98
- package/src/inspector/custom-element.tsx +1 -1
- package/src/inspector/index.tsx +44 -0
- package/src/inspector/ui/button.tsx +15 -1
- package/src/inspector/ui/heading.tsx +7 -2
- package/src/inspector/ui/input.tsx +6 -2
- package/src/inspector/ui/modal.tsx +158 -0
- package/src/inspector/viewer/delete-local-data.tsx +101 -0
- package/src/inspector/viewer/new-app.tsx +3 -19
- package/src/react/hooks.tsx +1 -0
- package/src/react/index.ts +1 -0
- package/src/react-core/hooks.ts +162 -0
- package/src/react-core/tests/useCoStateWithSelector.test.ts +149 -0
- package/src/react-native-core/hooks.tsx +1 -0
- package/src/tools/coValues/CoValueBase.ts +32 -0
- package/src/tools/coValues/coList.ts +35 -0
- package/src/tools/coValues/coMap.ts +0 -18
- package/src/tools/coValues/inbox.ts +190 -108
- package/src/tools/implementation/createContext.ts +9 -2
- package/src/tools/testing.ts +1 -1
- package/src/tools/tests/coFeed.test.ts +33 -22
- package/src/tools/tests/coList.test.ts +47 -4
- package/src/tools/tests/coMap.test.ts +13 -5
- package/src/tools/tests/coPlainText.test.ts +24 -0
- package/src/tools/tests/createContext.test.ts +24 -0
- package/src/tools/tests/deepLoading.test.ts +2 -0
- package/src/tools/tests/exportImport.test.ts +3 -1
- package/src/tools/tests/groupsAndAccounts.test.ts +56 -44
- package/src/tools/tests/inbox.test.ts +293 -31
- package/src/tools/tests/patterns/requestToJoin.test.ts +14 -6
- package/src/tools/tests/utils.ts +1 -0
- package/src/worker/index.ts +21 -5
- package/tsup.config.ts +1 -1
- package/dist/chunk-3LE7N6TH.js.map +0 -1
- package/dist/inspector/custom-element-WCY6D3QJ.js.map +0 -1
- package/src/inspector/index.ts +0 -23
- /package/src/inspector/viewer/{inpsector-button.tsx → inspector-button.tsx} +0 -0
@@ -54,6 +54,7 @@ describe("createContext methods", () => {
|
|
54
54
|
peersToLoadFrom: [getPeerConnectedToTestSyncServer()],
|
55
55
|
crypto: Crypto,
|
56
56
|
sessionProvider: randomSessionProvider,
|
57
|
+
asActiveAccount: true,
|
57
58
|
});
|
58
59
|
|
59
60
|
expect(context.node).toBeDefined();
|
@@ -86,6 +87,7 @@ describe("createContext methods", () => {
|
|
86
87
|
crypto: Crypto,
|
87
88
|
AccountSchema: CustomAccount,
|
88
89
|
sessionProvider: randomSessionProvider,
|
90
|
+
asActiveAccount: true,
|
89
91
|
});
|
90
92
|
|
91
93
|
expect(context.account).toBeInstanceOf(
|
@@ -109,6 +111,7 @@ describe("createContext methods", () => {
|
|
109
111
|
crypto: Crypto,
|
110
112
|
sessionProvider: randomSessionProvider,
|
111
113
|
onLogOut,
|
114
|
+
asActiveAccount: true,
|
112
115
|
});
|
113
116
|
|
114
117
|
context.logOut();
|
@@ -131,6 +134,7 @@ describe("createContext methods", () => {
|
|
131
134
|
peersToLoadFrom: [getPeerConnectedToTestSyncServer()],
|
132
135
|
crypto: Crypto,
|
133
136
|
sessionProvider: randomSessionProvider,
|
137
|
+
asActiveAccount: true,
|
134
138
|
});
|
135
139
|
|
136
140
|
const loadedMap = await loadCoValueOrFail(context.node, coMap.id);
|
@@ -151,10 +155,30 @@ describe("createContext methods", () => {
|
|
151
155
|
peersToLoadFrom: [getPeerConnectedToTestSyncServer()],
|
152
156
|
crypto: Crypto,
|
153
157
|
sessionProvider: randomSessionProvider,
|
158
|
+
asActiveAccount: true,
|
154
159
|
});
|
155
160
|
|
156
161
|
expect(activeAccountContext.get()).toBe(context.account);
|
157
162
|
});
|
163
|
+
|
164
|
+
test("does not set the active account when asActiveAccount is false", async () => {
|
165
|
+
const account = await createJazzTestAccount({
|
166
|
+
isCurrentActiveAccount: false,
|
167
|
+
});
|
168
|
+
|
169
|
+
const context = await createJazzContextFromExistingCredentials({
|
170
|
+
credentials: {
|
171
|
+
accountID: account.$jazz.id,
|
172
|
+
secret: account.$jazz.localNode.getCurrentAgent().agentSecret,
|
173
|
+
},
|
174
|
+
peersToLoadFrom: [getPeerConnectedToTestSyncServer()],
|
175
|
+
crypto: Crypto,
|
176
|
+
sessionProvider: randomSessionProvider,
|
177
|
+
asActiveAccount: false,
|
178
|
+
});
|
179
|
+
|
180
|
+
expect(activeAccountContext.get()).not.toBe(context.account);
|
181
|
+
});
|
158
182
|
});
|
159
183
|
|
160
184
|
describe("createJazzContextForNewAccount", () => {
|
@@ -57,6 +57,7 @@ describe("Deep loading with depth arg", async () => {
|
|
57
57
|
sessionProvider: randomSessionProvider,
|
58
58
|
peersToLoadFrom: [initialAsPeer],
|
59
59
|
crypto: Crypto,
|
60
|
+
asActiveAccount: true,
|
60
61
|
});
|
61
62
|
|
62
63
|
const ownership = { owner: me };
|
@@ -287,6 +288,7 @@ test("Deep loading a record-like coMap", async () => {
|
|
287
288
|
sessionProvider: randomSessionProvider,
|
288
289
|
peersToLoadFrom: [initialAsPeer],
|
289
290
|
crypto: Crypto,
|
291
|
+
asActiveAccount: true,
|
290
292
|
});
|
291
293
|
|
292
294
|
const record = RecordLike.create(
|
@@ -1,9 +1,9 @@
|
|
1
1
|
import { WasmCrypto } from "cojson/crypto/WasmCrypto";
|
2
2
|
import { assert, beforeEach, describe, expect, test } from "vitest";
|
3
3
|
import { CoMap, Group, z } from "../exports.js";
|
4
|
-
import { Loaded, Ref, co } from "../internal.js";
|
4
|
+
import { Account, Loaded, Ref, co } from "../internal.js";
|
5
5
|
import { createJazzTestAccount, setupJazzTestSync } from "../testing.js";
|
6
|
-
import { setupTwoNodes, waitFor } from "./utils.js";
|
6
|
+
import { loadCoValueOrFail, setupTwoNodes, waitFor } from "./utils.js";
|
7
7
|
|
8
8
|
const Crypto = await WasmCrypto.create();
|
9
9
|
|
@@ -272,14 +272,20 @@ describe("Group inheritance", () => {
|
|
272
272
|
const bob = await createJazzTestAccount({});
|
273
273
|
await bob.$jazz.waitForAllCoValuesSync();
|
274
274
|
|
275
|
+
const loadedAlice = await Account.load(alice.$jazz.id);
|
276
|
+
const loadedBob = await Account.load(bob.$jazz.id);
|
277
|
+
|
278
|
+
assert(loadedBob);
|
279
|
+
assert(loadedAlice);
|
280
|
+
|
275
281
|
const parentGroup = Group.create();
|
276
282
|
// `parentGroup` has `alice` as a writer
|
277
|
-
parentGroup.addMember(
|
283
|
+
parentGroup.addMember(loadedAlice, "writer");
|
278
284
|
expect(parentGroup.getRoleOf(alice.$jazz.id)).toBe("writer");
|
279
285
|
|
280
286
|
const group = Group.create();
|
281
287
|
// `group` has `bob` as a reader
|
282
|
-
group.addMember(
|
288
|
+
group.addMember(loadedBob, "reader");
|
283
289
|
expect(group.getRoleOf(bob.$jazz.id)).toBe("reader");
|
284
290
|
|
285
291
|
group.addMember(parentGroup);
|
@@ -817,24 +823,26 @@ describe("Group.members", () => {
|
|
817
823
|
childGroup.addMember(bob, "reader");
|
818
824
|
expect(childGroup.getRoleOf(bob.$jazz.id)).toBe("reader");
|
819
825
|
|
820
|
-
|
821
|
-
expect.
|
822
|
-
|
823
|
-
|
824
|
-
|
826
|
+
await waitFor(() => {
|
827
|
+
expect(childGroup.members).toEqual([
|
828
|
+
expect.objectContaining({
|
829
|
+
account: expect.objectContaining({
|
830
|
+
$jazz: expect.objectContaining({
|
831
|
+
id: co.account().getMe().$jazz.id,
|
832
|
+
}),
|
825
833
|
}),
|
834
|
+
role: "admin",
|
826
835
|
}),
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
id: bob.$jazz.id,
|
836
|
+
expect.objectContaining({
|
837
|
+
account: expect.objectContaining({
|
838
|
+
$jazz: expect.objectContaining({
|
839
|
+
id: bob.$jazz.id,
|
840
|
+
}),
|
833
841
|
}),
|
842
|
+
role: "reader",
|
834
843
|
}),
|
835
|
-
|
836
|
-
|
837
|
-
]);
|
844
|
+
]);
|
845
|
+
});
|
838
846
|
});
|
839
847
|
|
840
848
|
test("should return the members of the parent group", async () => {
|
@@ -849,24 +857,26 @@ describe("Group.members", () => {
|
|
849
857
|
|
850
858
|
expect(childGroup.getRoleOf(bob.$jazz.id)).toBe("reader");
|
851
859
|
|
852
|
-
|
853
|
-
expect.
|
854
|
-
|
855
|
-
|
856
|
-
|
860
|
+
await waitFor(() => {
|
861
|
+
expect(childGroup.members).toEqual([
|
862
|
+
expect.objectContaining({
|
863
|
+
account: expect.objectContaining({
|
864
|
+
$jazz: expect.objectContaining({
|
865
|
+
id: co.account().getMe().$jazz.id,
|
866
|
+
}),
|
857
867
|
}),
|
868
|
+
role: "admin",
|
858
869
|
}),
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
id: bob.$jazz.id,
|
870
|
+
expect.objectContaining({
|
871
|
+
account: expect.objectContaining({
|
872
|
+
$jazz: expect.objectContaining({
|
873
|
+
id: bob.$jazz.id,
|
874
|
+
}),
|
865
875
|
}),
|
876
|
+
role: "reader",
|
866
877
|
}),
|
867
|
-
|
868
|
-
|
869
|
-
]);
|
878
|
+
]);
|
879
|
+
});
|
870
880
|
});
|
871
881
|
|
872
882
|
test("should not return everyone", async () => {
|
@@ -926,22 +936,24 @@ describe("Group.getDirectMembers", () => {
|
|
926
936
|
childGroup.addMember(parentGroup);
|
927
937
|
|
928
938
|
// Child group should inherit bob through parent, but bob is not a direct member
|
929
|
-
|
930
|
-
expect.
|
931
|
-
|
932
|
-
|
933
|
-
|
939
|
+
await waitFor(() => {
|
940
|
+
expect(childGroup.members).toEqual([
|
941
|
+
expect.objectContaining({
|
942
|
+
account: expect.objectContaining({
|
943
|
+
$jazz: expect.objectContaining({
|
944
|
+
id: co.account().getMe().$jazz.id,
|
945
|
+
}),
|
934
946
|
}),
|
935
947
|
}),
|
936
|
-
|
937
|
-
|
938
|
-
|
939
|
-
|
940
|
-
|
948
|
+
expect.objectContaining({
|
949
|
+
account: expect.objectContaining({
|
950
|
+
$jazz: expect.objectContaining({
|
951
|
+
id: bob.$jazz.id,
|
952
|
+
}),
|
941
953
|
}),
|
942
954
|
}),
|
943
|
-
|
944
|
-
|
955
|
+
]);
|
956
|
+
});
|
945
957
|
|
946
958
|
// directMembers should only show the admin, not the inherited bob
|
947
959
|
expect(childGroup.getDirectMembers()).toEqual([
|
@@ -1,12 +1,29 @@
|
|
1
|
-
import { describe, expect, it, vi } from "vitest";
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
2
2
|
import { Group, Inbox, InboxSender, z } from "../exports";
|
3
|
-
import {
|
3
|
+
import {
|
4
|
+
Account,
|
5
|
+
Loaded,
|
6
|
+
co,
|
7
|
+
coValueClassFromCoValueClassOrSchema,
|
8
|
+
} from "../internal";
|
4
9
|
import { setupTwoNodes, waitFor } from "./utils";
|
10
|
+
import {
|
11
|
+
createJazzTestAccount,
|
12
|
+
getPeerConnectedToTestSyncServer,
|
13
|
+
setupJazzTestSync,
|
14
|
+
} from "../testing";
|
15
|
+
import { cojsonInternals, LocalNode } from "cojson";
|
16
|
+
import { WasmCrypto } from "cojson/crypto/WasmCrypto";
|
5
17
|
|
6
18
|
const Message = co.map({
|
7
19
|
text: z.string(),
|
8
20
|
});
|
9
21
|
|
22
|
+
beforeEach(async () => {
|
23
|
+
await setupJazzTestSync();
|
24
|
+
vi.useRealTimers();
|
25
|
+
});
|
26
|
+
|
10
27
|
describe("Inbox", () => {
|
11
28
|
describe("Private profile", () => {
|
12
29
|
it("Should throw if the inbox owner profile is private", async () => {
|
@@ -211,10 +228,7 @@ describe("Inbox", () => {
|
|
211
228
|
|
212
229
|
unsubscribe();
|
213
230
|
|
214
|
-
expect(errorLogSpy).toHaveBeenCalledWith(
|
215
|
-
"Error processing inbox message",
|
216
|
-
expect.any(Error),
|
217
|
-
);
|
231
|
+
expect(errorLogSpy).toHaveBeenCalledWith(new Error("Failed"));
|
218
232
|
|
219
233
|
errorLogSpy.mockRestore();
|
220
234
|
});
|
@@ -298,7 +312,7 @@ describe("Inbox", () => {
|
|
298
312
|
expect(receivedMessages[0]?.text).toBe("Hello");
|
299
313
|
});
|
300
314
|
|
301
|
-
it("should retry failed messages", async () => {
|
315
|
+
it("should not retry failed messages", async () => {
|
302
316
|
const { clientAccount: sender, serverAccount: receiver } =
|
303
317
|
await setupTwoNodes();
|
304
318
|
|
@@ -320,25 +334,18 @@ describe("Inbox", () => {
|
|
320
334
|
let failures = 0;
|
321
335
|
|
322
336
|
// Subscribe to inbox messages
|
323
|
-
const unsubscribe = receiverInbox.subscribe(
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
throw new Error("Failed");
|
328
|
-
},
|
329
|
-
{ retries: 2 },
|
330
|
-
);
|
337
|
+
const unsubscribe = receiverInbox.subscribe(Message, async () => {
|
338
|
+
failures++;
|
339
|
+
throw new Error("Failed");
|
340
|
+
});
|
331
341
|
|
332
342
|
await expect(promise).rejects.toThrow();
|
333
|
-
expect(failures).toBe(
|
343
|
+
expect(failures).toBe(1);
|
334
344
|
const [failed] = Object.values(receiverInbox.failed.items).flat();
|
335
|
-
expect(failed?.value.errors.length).toBe(
|
345
|
+
expect(failed?.value.errors.length).toBe(1);
|
336
346
|
unsubscribe();
|
337
347
|
|
338
|
-
expect(errorLogSpy).toHaveBeenCalledWith(
|
339
|
-
"Error processing inbox message",
|
340
|
-
expect.any(Error),
|
341
|
-
);
|
348
|
+
expect(errorLogSpy).toHaveBeenCalledWith(new Error("Failed"));
|
342
349
|
|
343
350
|
errorLogSpy.mockRestore();
|
344
351
|
});
|
@@ -357,28 +364,283 @@ describe("Inbox", () => {
|
|
357
364
|
const errorLogSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
358
365
|
|
359
366
|
// Subscribe to inbox messages
|
360
|
-
const unsubscribe = receiverInbox.subscribe(
|
361
|
-
|
362
|
-
|
363
|
-
spy();
|
364
|
-
},
|
365
|
-
{ retries: 2 },
|
366
|
-
);
|
367
|
+
const unsubscribe = receiverInbox.subscribe(Message, async () => {
|
368
|
+
spy();
|
369
|
+
});
|
367
370
|
|
368
371
|
await waitFor(() => {
|
369
372
|
const [failed] = Object.values(receiverInbox.failed.items).flat();
|
370
373
|
|
371
|
-
expect(failed?.value.errors.length).toBe(
|
374
|
+
expect(failed?.value.errors.length).toBe(1);
|
372
375
|
});
|
373
376
|
|
374
377
|
expect(spy).not.toHaveBeenCalled();
|
375
378
|
unsubscribe();
|
376
379
|
|
377
380
|
expect(errorLogSpy).toHaveBeenCalledWith(
|
378
|
-
"
|
379
|
-
expect.any(Error),
|
381
|
+
new Error("Inbox: message co_z123234 is unavailable"),
|
380
382
|
);
|
381
383
|
|
382
384
|
errorLogSpy.mockRestore();
|
383
385
|
});
|
386
|
+
|
387
|
+
it("should skip processed messages when a large inbox is restarted", async () => {
|
388
|
+
cojsonInternals.setMaxRecommendedTxSize(100);
|
389
|
+
|
390
|
+
const sender = await createJazzTestAccount();
|
391
|
+
const receiver = await createJazzTestAccount();
|
392
|
+
|
393
|
+
await receiver.$jazz.waitForAllCoValuesSync();
|
394
|
+
|
395
|
+
const receiverInbox = await Inbox.load(receiver);
|
396
|
+
const inboxSender = await InboxSender.load(receiver.$jazz.id, sender);
|
397
|
+
|
398
|
+
const group = Group.create({ owner: receiver });
|
399
|
+
|
400
|
+
// This generates 4 chunks on the processed stream
|
401
|
+
for (let i = 0; i < 5; i++) {
|
402
|
+
inboxSender.sendMessage(Message.create({ text: `Hello ${i}` }, group));
|
403
|
+
}
|
404
|
+
|
405
|
+
inboxSender.sendMessage(Message.create({ text: `done` }, group));
|
406
|
+
|
407
|
+
await new Promise((resolve) => {
|
408
|
+
const unsubscribe = receiverInbox.subscribe(Message, async (message) => {
|
409
|
+
if (message.text === "done") {
|
410
|
+
resolve(true);
|
411
|
+
unsubscribe();
|
412
|
+
}
|
413
|
+
});
|
414
|
+
});
|
415
|
+
|
416
|
+
const accountId = receiver.$jazz.id;
|
417
|
+
const accountSecret =
|
418
|
+
receiver.$jazz.localNode.getCurrentAgent().agentSecret;
|
419
|
+
const sessionID = receiver.$jazz.localNode.currentSessionID;
|
420
|
+
|
421
|
+
await receiver.$jazz.waitForAllCoValuesSync();
|
422
|
+
await receiver.$jazz.localNode.gracefulShutdown();
|
423
|
+
|
424
|
+
const node = await LocalNode.withLoadedAccount({
|
425
|
+
accountID: accountId as any,
|
426
|
+
accountSecret: accountSecret,
|
427
|
+
peersToLoadFrom: [getPeerConnectedToTestSyncServer()],
|
428
|
+
crypto: receiver.$jazz.localNode.crypto,
|
429
|
+
sessionID: sessionID,
|
430
|
+
});
|
431
|
+
|
432
|
+
const reloadedInbox = await Inbox.load(Account.fromNode(node));
|
433
|
+
|
434
|
+
const subscribeEmitted = await new Promise((resolve) => {
|
435
|
+
const unsubscribe = reloadedInbox.subscribe(Message, async (message) => {
|
436
|
+
// Got a message
|
437
|
+
resolve(true);
|
438
|
+
});
|
439
|
+
setTimeout(() => {
|
440
|
+
resolve(false);
|
441
|
+
unsubscribe();
|
442
|
+
}, 100);
|
443
|
+
});
|
444
|
+
|
445
|
+
expect(subscribeEmitted).toBe(false);
|
446
|
+
});
|
447
|
+
|
448
|
+
it("should skip failed messages when a large inbox is restarted", async () => {
|
449
|
+
cojsonInternals.setMaxRecommendedTxSize(100);
|
450
|
+
|
451
|
+
const sender = await createJazzTestAccount();
|
452
|
+
const receiver = await createJazzTestAccount();
|
453
|
+
|
454
|
+
await receiver.$jazz.waitForAllCoValuesSync();
|
455
|
+
|
456
|
+
const receiverInbox = await Inbox.load(receiver);
|
457
|
+
const inboxSender = await InboxSender.load(receiver.$jazz.id, sender);
|
458
|
+
|
459
|
+
const group = Group.create({ owner: receiver });
|
460
|
+
|
461
|
+
// This generates 4 chunks on the processed stream
|
462
|
+
for (let i = 0; i < 5; i++) {
|
463
|
+
inboxSender
|
464
|
+
.sendMessage(Message.create({ text: `Hello ${i}` }, group))
|
465
|
+
.catch(() => {});
|
466
|
+
}
|
467
|
+
|
468
|
+
inboxSender
|
469
|
+
.sendMessage(Message.create({ text: `done` }, group))
|
470
|
+
.catch(() => {});
|
471
|
+
|
472
|
+
await new Promise((resolve) => {
|
473
|
+
const unsubscribe = receiverInbox.subscribe(Message, async (message) => {
|
474
|
+
if (message.text === "done") {
|
475
|
+
resolve(true);
|
476
|
+
unsubscribe();
|
477
|
+
}
|
478
|
+
|
479
|
+
throw new Error("Failed");
|
480
|
+
});
|
481
|
+
});
|
482
|
+
|
483
|
+
await waitFor(() => {
|
484
|
+
expect(Object.values(receiverInbox.processed.items).length).toBe(1);
|
485
|
+
});
|
486
|
+
|
487
|
+
const accountId = receiver.$jazz.id;
|
488
|
+
const accountSecret =
|
489
|
+
receiver.$jazz.localNode.getCurrentAgent().agentSecret;
|
490
|
+
const sessionID = receiver.$jazz.localNode.currentSessionID;
|
491
|
+
|
492
|
+
await receiver.$jazz.waitForAllCoValuesSync();
|
493
|
+
await receiver.$jazz.localNode.gracefulShutdown();
|
494
|
+
|
495
|
+
const node = await LocalNode.withLoadedAccount({
|
496
|
+
accountID: accountId as any,
|
497
|
+
accountSecret: accountSecret,
|
498
|
+
peersToLoadFrom: [getPeerConnectedToTestSyncServer()],
|
499
|
+
crypto: receiver.$jazz.localNode.crypto,
|
500
|
+
sessionID: sessionID,
|
501
|
+
});
|
502
|
+
|
503
|
+
const reloadedInbox = await Inbox.load(Account.fromNode(node));
|
504
|
+
|
505
|
+
const subscribeEmitted = await new Promise((resolve) => {
|
506
|
+
const unsubscribe = reloadedInbox.subscribe(Message, async (message) => {
|
507
|
+
// Got a message
|
508
|
+
resolve(true);
|
509
|
+
});
|
510
|
+
setTimeout(() => {
|
511
|
+
resolve(false);
|
512
|
+
unsubscribe();
|
513
|
+
}, 100);
|
514
|
+
});
|
515
|
+
|
516
|
+
expect(subscribeEmitted).toBe(false);
|
517
|
+
});
|
518
|
+
|
519
|
+
describe("Concurrency control", () => {
|
520
|
+
it("should respect concurrency limit of 1", async () => {
|
521
|
+
const { clientAccount: sender, serverAccount: receiver } =
|
522
|
+
await setupTwoNodes();
|
523
|
+
|
524
|
+
const receiverInbox = await Inbox.load(receiver);
|
525
|
+
const inboxSender = await InboxSender.load(receiver.$jazz.id, sender);
|
526
|
+
|
527
|
+
const group = Group.create({ owner: sender });
|
528
|
+
|
529
|
+
const processingOrder: string[] = [];
|
530
|
+
|
531
|
+
const Message = co.map({
|
532
|
+
text: z.string(),
|
533
|
+
value: z.number(),
|
534
|
+
});
|
535
|
+
|
536
|
+
// Create messages that take time to process
|
537
|
+
const messages = Array.from({ length: 5 }, (_, i) =>
|
538
|
+
Message.create({ text: `Message ${i}`, value: i }, group),
|
539
|
+
);
|
540
|
+
|
541
|
+
// Subscribe with concurrency limit of 1
|
542
|
+
const unsubscribe = receiverInbox.subscribe(
|
543
|
+
Message,
|
544
|
+
async (message) => {
|
545
|
+
const messageText = message.text;
|
546
|
+
processingOrder.push(`start-${messageText}`);
|
547
|
+
|
548
|
+
// Simulate processing time
|
549
|
+
await new Promise((resolve) =>
|
550
|
+
setTimeout(resolve, 20 - message.value * 10),
|
551
|
+
);
|
552
|
+
|
553
|
+
processingOrder.push(`end-${messageText}`);
|
554
|
+
},
|
555
|
+
{ concurrencyLimit: 1 },
|
556
|
+
);
|
557
|
+
|
558
|
+
// Send all messages at once
|
559
|
+
messages.forEach((message) => {
|
560
|
+
inboxSender.sendMessage(message);
|
561
|
+
});
|
562
|
+
|
563
|
+
// Wait for all messages to be processed
|
564
|
+
await waitFor(() => processingOrder.length === 10); // 5 start + 5 end
|
565
|
+
|
566
|
+
expect(processingOrder).toMatchInlineSnapshot(`
|
567
|
+
[
|
568
|
+
"start-Message 0",
|
569
|
+
"end-Message 0",
|
570
|
+
"start-Message 1",
|
571
|
+
"end-Message 1",
|
572
|
+
"start-Message 2",
|
573
|
+
"end-Message 2",
|
574
|
+
"start-Message 3",
|
575
|
+
"end-Message 3",
|
576
|
+
"start-Message 4",
|
577
|
+
"end-Message 4",
|
578
|
+
]
|
579
|
+
`);
|
580
|
+
unsubscribe();
|
581
|
+
});
|
582
|
+
|
583
|
+
it("should allow concurrent processing with higher concurrency limit", async () => {
|
584
|
+
const { clientAccount: sender, serverAccount: receiver } =
|
585
|
+
await setupTwoNodes();
|
586
|
+
|
587
|
+
const receiverInbox = await Inbox.load(receiver);
|
588
|
+
const inboxSender = await InboxSender.load(receiver.$jazz.id, sender);
|
589
|
+
|
590
|
+
const group = Group.create({ owner: sender });
|
591
|
+
|
592
|
+
// Track processing order and timing
|
593
|
+
const processingOrder: string[] = [];
|
594
|
+
|
595
|
+
const Message = co.map({
|
596
|
+
text: z.string(),
|
597
|
+
value: z.number(),
|
598
|
+
});
|
599
|
+
|
600
|
+
// Create messages that take time to process
|
601
|
+
const messages = Array.from({ length: 5 }, (_, i) =>
|
602
|
+
Message.create({ text: `Message ${i}`, value: i }, group),
|
603
|
+
);
|
604
|
+
|
605
|
+
// Subscribe with concurrency limit of 1
|
606
|
+
const unsubscribe = receiverInbox.subscribe(
|
607
|
+
Message,
|
608
|
+
async (message) => {
|
609
|
+
const messageText = message.text;
|
610
|
+
processingOrder.push(`start-${messageText}`);
|
611
|
+
|
612
|
+
// Simulate processing time
|
613
|
+
await new Promise((resolve) =>
|
614
|
+
setTimeout(resolve, 20 - message.value * 10),
|
615
|
+
);
|
616
|
+
|
617
|
+
processingOrder.push(`end-${messageText}`);
|
618
|
+
},
|
619
|
+
{ concurrencyLimit: 3 },
|
620
|
+
);
|
621
|
+
|
622
|
+
// Send all messages at once
|
623
|
+
messages.forEach((message) => {
|
624
|
+
inboxSender.sendMessage(message);
|
625
|
+
});
|
626
|
+
|
627
|
+
await waitFor(() => processingOrder.length === 10);
|
628
|
+
|
629
|
+
expect(processingOrder).toMatchInlineSnapshot(`
|
630
|
+
[
|
631
|
+
"start-Message 0",
|
632
|
+
"start-Message 1",
|
633
|
+
"start-Message 2",
|
634
|
+
"end-Message 2",
|
635
|
+
"start-Message 3",
|
636
|
+
"end-Message 3",
|
637
|
+
"start-Message 4",
|
638
|
+
"end-Message 4",
|
639
|
+
"end-Message 1",
|
640
|
+
"end-Message 0",
|
641
|
+
]
|
642
|
+
`);
|
643
|
+
unsubscribe();
|
644
|
+
});
|
645
|
+
});
|
384
646
|
});
|
@@ -1,6 +1,10 @@
|
|
1
1
|
import { assert, describe, expect, test } from "vitest";
|
2
2
|
import { Account, Group, co, z } from "../../exports";
|
3
|
-
import {
|
3
|
+
import {
|
4
|
+
createJazzTestAccount,
|
5
|
+
linkAccounts,
|
6
|
+
setupJazzTestSync,
|
7
|
+
} from "../../testing";
|
4
8
|
|
5
9
|
const RequestToJoin = co.map({
|
6
10
|
account: Account,
|
@@ -24,16 +28,20 @@ const Organization = co.map({
|
|
24
28
|
});
|
25
29
|
|
26
30
|
async function setup() {
|
31
|
+
await setupJazzTestSync();
|
32
|
+
|
27
33
|
const admin1 = await createJazzTestAccount();
|
28
34
|
const admin2 = await createJazzTestAccount();
|
29
35
|
const user1 = await createJazzTestAccount();
|
30
36
|
const user2 = await createJazzTestAccount();
|
31
37
|
|
32
|
-
|
33
|
-
|
34
|
-
await linkAccounts(admin1,
|
35
|
-
await linkAccounts(
|
36
|
-
await linkAccounts(
|
38
|
+
// TODO: with this setting the waitForAllCoValuesSync gets stuck
|
39
|
+
// https://github.com/garden-co/jazz/issues/2874
|
40
|
+
// await linkAccounts(admin1, admin2);
|
41
|
+
// await linkAccounts(admin1, user1);
|
42
|
+
// await linkAccounts(admin1, user2);
|
43
|
+
// await linkAccounts(admin2, user1);
|
44
|
+
// await linkAccounts(admin2, user2);
|
37
45
|
|
38
46
|
// The organization info are public
|
39
47
|
const adminsGroup = Group.create(admin1);
|