jazz-tools 0.8.3 → 0.8.11

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 (39) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/native/coValues/interfaces.js +15 -0
  3. package/dist/native/coValues/interfaces.js.map +1 -1
  4. package/dist/native/exports.js +11 -0
  5. package/dist/native/exports.js.map +1 -0
  6. package/dist/native/implementation/refs.js +4 -4
  7. package/dist/native/implementation/refs.js.map +1 -1
  8. package/dist/native/implementation/schema.js +14 -9
  9. package/dist/native/implementation/schema.js.map +1 -1
  10. package/dist/native/implementation/subscriptionScope.js +8 -9
  11. package/dist/native/implementation/subscriptionScope.js.map +1 -1
  12. package/dist/native/index.native.js +1 -10
  13. package/dist/native/index.native.js.map +1 -1
  14. package/dist/web/coValues/interfaces.js +15 -0
  15. package/dist/web/coValues/interfaces.js.map +1 -1
  16. package/dist/web/exports.js +11 -0
  17. package/dist/web/exports.js.map +1 -0
  18. package/dist/web/implementation/refs.js +4 -4
  19. package/dist/web/implementation/refs.js.map +1 -1
  20. package/dist/web/implementation/schema.js +14 -9
  21. package/dist/web/implementation/schema.js.map +1 -1
  22. package/dist/web/implementation/subscriptionScope.js +8 -9
  23. package/dist/web/implementation/subscriptionScope.js.map +1 -1
  24. package/dist/web/index.web.js +1 -10
  25. package/dist/web/index.web.js.map +1 -1
  26. package/package.json +6 -6
  27. package/src/coValues/interfaces.ts +34 -0
  28. package/src/exports.ts +35 -0
  29. package/src/implementation/refs.ts +3 -8
  30. package/src/implementation/schema.ts +21 -13
  31. package/src/implementation/subscriptionScope.ts +10 -11
  32. package/src/index.native.ts +2 -35
  33. package/src/index.web.ts +2 -35
  34. package/src/tests/coMap.test.ts +1 -1
  35. package/src/tests/schema.test.ts +205 -0
  36. package/src/tests/subscribe.test.ts +348 -0
  37. package/.turbo/turbo-build.log +0 -8
  38. package/.turbo/turbo-lint.log +0 -4
  39. package/.turbo/turbo-test.log +0 -144
@@ -0,0 +1,348 @@
1
+ const Crypto = await WasmCrypto.create();
2
+ import { expect, describe, it, vi, onTestFinished } from "vitest";
3
+ import { connectedPeers } from "cojson/src/streamUtils.js";
4
+ import {
5
+ Account,
6
+ CoList,
7
+ CoMap,
8
+ CoStream,
9
+ WasmCrypto,
10
+ co,
11
+ isControlledAccount,
12
+ createJazzContext,
13
+ fixedCredentialsAuth,
14
+ } from "../index.web.js";
15
+ import {
16
+ BinaryCoStream,
17
+ Group,
18
+ randomSessionProvider,
19
+ subscribeToCoValue,
20
+ } from "../internal.js";
21
+
22
+ class ChatRoom extends CoMap {
23
+ messages = co.ref(MessagesList);
24
+ name = co.string;
25
+ }
26
+
27
+ class Message extends CoMap {
28
+ text = co.string;
29
+ reactions = co.ref(ReactionsStream);
30
+ attachment = co.optional.ref(BinaryCoStream);
31
+ }
32
+
33
+ class MessagesList extends CoList.Of(co.ref(Message)) {}
34
+ class ReactionsStream extends CoStream.Of(co.string) {}
35
+
36
+ async function setupAccount() {
37
+ const me = await Account.create({
38
+ creationProps: { name: "Hermes Puggington" },
39
+ crypto: Crypto,
40
+ });
41
+
42
+ const [initialAsPeer, secondPeer] = connectedPeers("initial", "second", {
43
+ peer1role: "server",
44
+ peer2role: "client",
45
+ });
46
+
47
+ if (!isControlledAccount(me)) {
48
+ throw "me is not a controlled account";
49
+ }
50
+ me._raw.core.node.syncManager.addPeer(secondPeer);
51
+ const { account: meOnSecondPeer } = await createJazzContext({
52
+ auth: fixedCredentialsAuth({
53
+ accountID: me.id,
54
+ secret: me._raw.agentSecret,
55
+ }),
56
+ sessionProvider: randomSessionProvider,
57
+ peersToLoadFrom: [initialAsPeer],
58
+ crypto: Crypto,
59
+ });
60
+
61
+ return { me, meOnSecondPeer };
62
+ }
63
+
64
+ function createChatRoom(me: Account | Group, name: string) {
65
+ return ChatRoom.create(
66
+ { messages: MessagesList.create([], { owner: me }), name },
67
+ { owner: me },
68
+ );
69
+ }
70
+
71
+ function createMessage(me: Account | Group, text: string) {
72
+ return Message.create(
73
+ { text, reactions: ReactionsStream.create([], { owner: me }) },
74
+ { owner: me },
75
+ );
76
+ }
77
+
78
+ describe("subscribeToCoValue", () => {
79
+ it("subscribes to a CoMap", async () => {
80
+ const { me, meOnSecondPeer } = await setupAccount();
81
+
82
+ const chatRoom = createChatRoom(me, "General");
83
+ const updateFn = vi.fn();
84
+
85
+ const unsubscribe = subscribeToCoValue(
86
+ ChatRoom,
87
+ chatRoom.id,
88
+ meOnSecondPeer,
89
+ {},
90
+ updateFn,
91
+ );
92
+
93
+ onTestFinished(unsubscribe);
94
+
95
+ await waitFor(() => {
96
+ expect(updateFn).toHaveBeenCalled();
97
+ });
98
+
99
+ expect(updateFn).toHaveBeenCalledWith(
100
+ expect.objectContaining({
101
+ id: chatRoom.id,
102
+ messages: null,
103
+ name: "General",
104
+ }),
105
+ );
106
+
107
+ updateFn.mockClear();
108
+
109
+ await waitFor(() => {
110
+ expect(updateFn).toHaveBeenCalled();
111
+ });
112
+
113
+ expect(updateFn).toHaveBeenCalledWith(
114
+ expect.objectContaining({
115
+ id: chatRoom.id,
116
+ name: "General",
117
+ messages: expect.any(Array),
118
+ }),
119
+ );
120
+
121
+ updateFn.mockClear();
122
+ chatRoom.name = "Lounge";
123
+
124
+ await waitFor(() => {
125
+ expect(updateFn).toHaveBeenCalled();
126
+ });
127
+
128
+ expect(updateFn).toHaveBeenCalledWith(
129
+ expect.objectContaining({
130
+ id: chatRoom.id,
131
+ name: "Lounge",
132
+ messages: expect.any(Array),
133
+ }),
134
+ );
135
+ });
136
+
137
+ it("shouldn't fire updates until the declared load depth isn't reached", async () => {
138
+ const { me, meOnSecondPeer } = await setupAccount();
139
+
140
+ const chatRoom = createChatRoom(me, "General");
141
+ const updateFn = vi.fn();
142
+
143
+ const unsubscribe = subscribeToCoValue(
144
+ ChatRoom,
145
+ chatRoom.id,
146
+ meOnSecondPeer,
147
+ {
148
+ messages: [],
149
+ },
150
+ updateFn,
151
+ );
152
+
153
+ onTestFinished(unsubscribe);
154
+
155
+ await waitFor(() => {
156
+ expect(updateFn).toHaveBeenCalled();
157
+ });
158
+
159
+ expect(updateFn).toHaveBeenCalledTimes(1);
160
+ expect(updateFn).toHaveBeenCalledWith(
161
+ expect.objectContaining({
162
+ id: chatRoom.id,
163
+ name: "General",
164
+ messages: expect.any(Array),
165
+ }),
166
+ );
167
+ });
168
+
169
+ it("should fire updates when a ref entity is updates", async () => {
170
+ const { me, meOnSecondPeer } = await setupAccount();
171
+
172
+ const chatRoom = createChatRoom(me, "General");
173
+ const message = createMessage(me, "Hello Luigi, are you ready to save the princess?");
174
+ chatRoom.messages?.push(message);
175
+
176
+ const updateFn = vi.fn()
177
+
178
+ const unsubscribe = subscribeToCoValue(
179
+ ChatRoom,
180
+ chatRoom.id,
181
+ meOnSecondPeer,
182
+ {
183
+ messages: [{
184
+ }],
185
+ },
186
+ updateFn,
187
+ );
188
+
189
+ onTestFinished(unsubscribe);
190
+
191
+ await waitFor(() => {
192
+ const lastValue = updateFn.mock.lastCall[0];
193
+
194
+ expect(lastValue?.messages?.[0]?.text).toBe(message.text);
195
+ });
196
+
197
+ message.text = "Nevermind, she was gone to the supermarket";
198
+ updateFn.mockClear();
199
+
200
+ await waitFor(() => {
201
+ expect(updateFn).toHaveBeenCalled();
202
+ });
203
+
204
+ const lastValue = updateFn.mock.lastCall[0];
205
+ expect(lastValue?.messages?.[0]?.text).toBe("Nevermind, she was gone to the supermarket");
206
+ });
207
+
208
+ it("should handle the updates as immutable changes", async () => {
209
+ const { me, meOnSecondPeer } = await setupAccount();
210
+
211
+ const chatRoom = createChatRoom(me, "General");
212
+ const message = createMessage(me, "Hello Luigi, are you ready to save the princess?");
213
+ const message2 = createMessage(me, "Let's go!");
214
+ chatRoom.messages?.push(message);
215
+ chatRoom.messages?.push(message2);
216
+
217
+ const updateFn = vi.fn()
218
+
219
+ const unsubscribe = subscribeToCoValue(
220
+ ChatRoom,
221
+ chatRoom.id,
222
+ meOnSecondPeer,
223
+ {
224
+ messages: [{
225
+ reactions: [],
226
+ }],
227
+ },
228
+ updateFn,
229
+ );
230
+
231
+ onTestFinished(unsubscribe);
232
+
233
+ await waitFor(() => {
234
+ const lastValue = updateFn.mock.lastCall[0];
235
+
236
+ expect(lastValue?.messages?.[0]?.text).toBe(message.text);
237
+ });
238
+
239
+ const initialValue = updateFn.mock.lastCall[0];
240
+ const initialMessagesList = initialValue?.messages;
241
+ const initialMessage1 = initialValue?.messages[0];
242
+ const initialMessage2 = initialValue?.messages[1];
243
+ const initialMessageReactions = initialValue?.messages[0].reactions;
244
+
245
+ message.reactions?.push("👍");
246
+
247
+ updateFn.mockClear();
248
+
249
+ await waitFor(() => {
250
+ expect(updateFn).toHaveBeenCalled();
251
+ });
252
+
253
+ const lastValue = updateFn.mock.lastCall[0];
254
+ expect(lastValue).not.toBe(initialValue);
255
+ expect(lastValue.messages).not.toBe(initialMessagesList);
256
+ expect(lastValue.messages[0]).not.toBe(initialMessage1);
257
+ expect(lastValue.messages[0].reactions).not.toBe(initialMessageReactions);
258
+
259
+ // This shouldn't change
260
+ expect(lastValue.messages[1]).toBe(initialMessage2);
261
+
262
+ // TODO: The initial should point at that snapshot in time
263
+ // expect(lastValue.messages).not.toBe(initialValue.messages);
264
+ // expect(lastValue.messages[0]).not.toBe(initialValue.messages[0]);
265
+ // expect(lastValue.messages[1]).toBe(initialValue.messages[1]);
266
+ // expect(lastValue.messages[0].reactions).not.toBe(initialValue.messages[0].reactions);
267
+ });
268
+
269
+ it("should keep the same identity on the ref entities when a property is updated", async () => {
270
+ const { me, meOnSecondPeer } = await setupAccount();
271
+
272
+ const chatRoom = createChatRoom(me, "General");
273
+ const message = createMessage(me, "Hello Luigi, are you ready to save the princess?");
274
+ const message2 = createMessage(me, "Let's go!");
275
+ chatRoom.messages?.push(message);
276
+ chatRoom.messages?.push(message2);
277
+
278
+ const updateFn = vi.fn()
279
+
280
+ const unsubscribe = subscribeToCoValue(
281
+ ChatRoom,
282
+ chatRoom.id,
283
+ meOnSecondPeer,
284
+ {
285
+ messages: [{
286
+ reactions: [],
287
+ }],
288
+ },
289
+ updateFn,
290
+ );
291
+
292
+ onTestFinished(unsubscribe);
293
+
294
+ await waitFor(() => {
295
+ const lastValue = updateFn.mock.lastCall[0];
296
+
297
+ expect(lastValue?.messages?.[0]?.text).toBe(message.text);
298
+ expect(lastValue?.messages?.[1]?.text).toBe(message2.text);
299
+ });
300
+
301
+ const initialValue = updateFn.mock.lastCall[0];
302
+ chatRoom.name = "Me and Luigi";
303
+
304
+ updateFn.mockClear();
305
+
306
+ await waitFor(() => {
307
+ expect(updateFn).toHaveBeenCalled();
308
+ });
309
+
310
+ const lastValue = updateFn.mock.lastCall[0];
311
+ expect(lastValue).not.toBe(initialValue);
312
+ expect(lastValue.name).toBe("Me and Luigi");
313
+ expect(initialValue.name).toBe("General");
314
+
315
+ expect(lastValue.messages).toBe(initialValue.messages);
316
+ expect(lastValue.messages[0]).toBe(initialValue.messages[0]);
317
+ expect(lastValue.messages[1]).toBe(initialValue.messages[1]);
318
+ });
319
+ });
320
+
321
+
322
+ function waitFor(callback: () => boolean | void) {
323
+ return new Promise<void>((resolve, reject) => {
324
+ const checkPassed = () => {
325
+ try {
326
+ return { ok: callback(), error: null };
327
+ } catch (error) {
328
+ return { ok: false, error };
329
+ }
330
+ };
331
+
332
+ let retries = 0;
333
+
334
+ const interval = setInterval(() => {
335
+ const { ok, error } = checkPassed();
336
+
337
+ if (ok !== false) {
338
+ clearInterval(interval);
339
+ resolve();
340
+ }
341
+
342
+ if (++retries > 10) {
343
+ clearInterval(interval);
344
+ reject(error);
345
+ }
346
+ }, 100);
347
+ });
348
+ }
@@ -1,8 +0,0 @@
1
-
2
- > jazz-tools@0.8.1 build /Users/anselm/jazz/jazz/packages/jazz-tools
3
- > npm run lint && rm -rf ./dist && tsc --sourceMap --outDir dist
4
-
5
-
6
- > jazz-tools@0.8.1 lint
7
- > eslint . --ext ts,tsx
8
-
@@ -1,4 +0,0 @@
1
-
2
- > jazz-tools@0.7.35-guest-auth.5 lint /Users/anselm/jazz/jazz/packages/jazz-tools
3
- > eslint . --ext ts,tsx
4
-
@@ -1,144 +0,0 @@
1
-
2
- > jazz-tools@0.7.35-unique.2 test /Users/anselm/jazz/jazz/packages/jazz-tools
3
- > vitest --run
4
-
5
-
6
- RUN v0.34.6 /Users/anselm/jazz/jazz/packages/jazz-tools
7
-
8
- ✓ src/tests/groupsAndAccounts.test.ts (1 test) 64ms
9
- stdout | unknown test
10
- TestMap schema undefined
11
-
12
- ✓ src/tests/deepLoading.test.ts (3 tests) 83ms
13
- stdout | src/tests/coList.test.ts > Simple CoList operations > Mutation > splice
14
- [ 'bread', 'onion' ] 0
15
- [ 'bread', 'salt', 'onion' ] 1
16
-
17
- stdout | src/tests/coList.test.ts > CoList resolution > Subscription & auto-resolution
18
- subscribedList?.[0]?.[0]?.[0] undefined
19
- subscribedList?.[0]?.[0]?.[0] undefined
20
- subscribedList?.[0]?.[0]?.[0] a
21
- subscribedList?.[0]?.[0]?.[0] x
22
- subscribedList?.[0]?.[0]?.[0] y
23
- subscribedList?.[0]?.[0]?.[0] w
24
-
25
- ✓ src/tests/coList.test.ts (10 tests) 190ms
26
- stdout | src/tests/coStream.test.ts > CoStream resolution > Subscription & auto-resolution
27
- subscribedStream[me.id] {
28
- value: [Getter],
29
- ref: [Getter],
30
- by: [Getter],
31
- madeAt: 2024-09-05T17:35:38.414Z,
32
- tx: {
33
- sessionID: 'co_zURWEpvoH1e35FGHVxadgXKU8Ri_session_ziAMdGVstAxF',
34
- txIndex: 0
35
- }
36
- }
37
- subscribedStream[me.id]?.value?.[me.id]?.value undefined
38
- subscribedStream[me.id]?.value?.[me.id]?.value?.[me.id]?.value undefined
39
- subscribedStream[me.id] {
40
- value: [Getter],
41
- ref: [Getter],
42
- by: [Getter],
43
- madeAt: 2024-09-05T17:35:38.414Z,
44
- tx: {
45
- sessionID: 'co_zURWEpvoH1e35FGHVxadgXKU8Ri_session_ziAMdGVstAxF',
46
- txIndex: 0
47
- }
48
- }
49
- subscribedStream[me.id]?.value?.[me.id]?.value null
50
- subscribedStream[me.id]?.value?.[me.id]?.value?.[me.id]?.value undefined
51
- subscribedStream[me.id] {
52
- value: [Getter],
53
- ref: [Getter],
54
- by: [Getter],
55
- madeAt: 2024-09-05T17:35:38.414Z,
56
- tx: {
57
- sessionID: 'co_zURWEpvoH1e35FGHVxadgXKU8Ri_session_ziAMdGVstAxF',
58
- txIndex: 0
59
- }
60
- }
61
- subscribedStream[me.id]?.value?.[me.id]?.value {
62
- id: 'co_zHELVqRmAN6YuGDpKqVrSXJ5qe1',
63
- _type: 'CoStream',
64
- perSession: undefined,
65
- co_zURWEpvoH1e35FGHVxadgXKU8Ri: 'milk',
66
- in: { co_zURWEpvoH1e35FGHVxadgXKU8Ri_session_ziAMdGVstAxF: 'milk' }
67
- }
68
- subscribedStream[me.id]?.value?.[me.id]?.value?.[me.id]?.value milk
69
- subscribedStream[me.id] {
70
- value: [Getter],
71
- ref: [Getter],
72
- by: [Getter],
73
- madeAt: 2024-09-05T17:35:38.414Z,
74
- tx: {
75
- sessionID: 'co_zURWEpvoH1e35FGHVxadgXKU8Ri_session_ziAMdGVstAxF',
76
- txIndex: 0
77
- }
78
- }
79
- subscribedStream[me.id]?.value?.[me.id]?.value {
80
- id: 'co_zHELVqRmAN6YuGDpKqVrSXJ5qe1',
81
- _type: 'CoStream',
82
- perSession: undefined,
83
- co_zURWEpvoH1e35FGHVxadgXKU8Ri: 'bread',
84
- in: {
85
- co_zURWEpvoH1e35FGHVxadgXKU8Ri_session_ziAMdGVstAxF: 'milk',
86
- co_zURWEpvoH1e35FGHVxadgXKU8Ri_session_zgik2JjD86Qa: 'bread'
87
- }
88
- }
89
- subscribedStream[me.id]?.value?.[me.id]?.value?.[me.id]?.value bread
90
- subscribedStream[me.id] {
91
- value: [Getter],
92
- ref: [Getter],
93
- by: [Getter],
94
- madeAt: 2024-09-05T17:35:38.424Z,
95
- tx: {
96
- sessionID: 'co_zURWEpvoH1e35FGHVxadgXKU8Ri_session_zgik2JjD86Qa',
97
- txIndex: 0
98
- }
99
- }
100
- subscribedStream[me.id]?.value?.[me.id]?.value {
101
- id: 'co_zJmdvJY15XJkWF3n3rwPgGfebxa',
102
- _type: 'CoStream',
103
- perSession: undefined,
104
- co_zURWEpvoH1e35FGHVxadgXKU8Ri: 'butter',
105
- in: { co_zURWEpvoH1e35FGHVxadgXKU8Ri_session_zgik2JjD86Qa: 'butter' }
106
- }
107
- subscribedStream[me.id]?.value?.[me.id]?.value?.[me.id]?.value butter
108
- subscribedStream[me.id] {
109
- value: [Getter],
110
- ref: [Getter],
111
- by: [Getter],
112
- madeAt: 2024-09-05T17:35:38.424Z,
113
- tx: {
114
- sessionID: 'co_zURWEpvoH1e35FGHVxadgXKU8Ri_session_zgik2JjD86Qa',
115
- txIndex: 0
116
- }
117
- }
118
- subscribedStream[me.id]?.value?.[me.id]?.value {
119
- id: 'co_zJmdvJY15XJkWF3n3rwPgGfebxa',
120
- _type: 'CoStream',
121
- perSession: undefined,
122
- co_zURWEpvoH1e35FGHVxadgXKU8Ri: 'jam',
123
- in: { co_zURWEpvoH1e35FGHVxadgXKU8Ri_session_zgik2JjD86Qa: 'jam' }
124
- }
125
- subscribedStream[me.id]?.value?.[me.id]?.value?.[me.id]?.value jam
126
-
127
- ✓ src/tests/coStream.test.ts (10 tests) 205ms
128
- stdout | src/tests/coMap.test.ts > CoMap resolution > Subscription & auto-resolution
129
- subscribedMap.nested?.twiceNested?.taste undefined
130
- subscribedMap.nested?.twiceNested?.taste undefined
131
- subscribedMap.nested?.twiceNested?.taste sour
132
- subscribedMap.nested?.twiceNested?.taste sour
133
- subscribedMap.nested?.twiceNested?.taste sweet
134
- subscribedMap.nested?.twiceNested?.taste sweet
135
- subscribedMap.nested?.twiceNested?.taste salty
136
- subscribedMap.nested?.twiceNested?.taste umami
137
-
138
- ✓ src/tests/coMap.test.ts (20 tests) 257ms
139
-
140
- Test Files 5 passed (5)
141
- Tests 44 passed (44)
142
- Start at 18:35:37
143
- Duration 929ms (transform 419ms, setup 0ms, collect 2.29s, tests 799ms, environment 0ms, prepare 298ms)
144
-