jazz-tools 0.18.17 → 0.18.18

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 (90) hide show
  1. package/.svelte-kit/__package__/jazz.class.svelte.d.ts +14 -0
  2. package/.svelte-kit/__package__/jazz.class.svelte.d.ts.map +1 -1
  3. package/.svelte-kit/__package__/jazz.class.svelte.js +37 -0
  4. package/.svelte-kit/__package__/testing.d.ts +1 -1
  5. package/.svelte-kit/__package__/testing.d.ts.map +1 -1
  6. package/.svelte-kit/__package__/testing.js +1 -1
  7. package/.svelte-kit/__package__/tests/TestConnectionStatus.svelte +8 -0
  8. package/.svelte-kit/__package__/tests/TestConnectionStatus.svelte.d.ts +27 -0
  9. package/.svelte-kit/__package__/tests/TestConnectionStatus.svelte.d.ts.map +1 -0
  10. package/.svelte-kit/__package__/tests/sync-connection-status.svelte.test.d.ts +2 -0
  11. package/.svelte-kit/__package__/tests/sync-connection-status.svelte.test.d.ts.map +1 -0
  12. package/.svelte-kit/__package__/tests/sync-connection-status.svelte.test.js +47 -0
  13. package/.turbo/turbo-build.log +47 -47
  14. package/CHANGELOG.md +16 -0
  15. package/dist/browser/BrowserContextManager.d.ts +4 -0
  16. package/dist/browser/BrowserContextManager.d.ts.map +1 -1
  17. package/dist/browser/createBrowserContext.d.ts +4 -0
  18. package/dist/browser/createBrowserContext.d.ts.map +1 -1
  19. package/dist/browser/index.js +36 -4
  20. package/dist/browser/index.js.map +1 -1
  21. package/dist/{chunk-OTWWOZMB.js → chunk-FHRKDKDY.js} +8 -3
  22. package/dist/chunk-FHRKDKDY.js.map +1 -0
  23. package/dist/index.js +1 -1
  24. package/dist/react/hooks.d.ts +1 -1
  25. package/dist/react/hooks.d.ts.map +1 -1
  26. package/dist/react/index.d.ts +1 -1
  27. package/dist/react/index.d.ts.map +1 -1
  28. package/dist/react/index.js +4 -2
  29. package/dist/react/index.js.map +1 -1
  30. package/dist/react-core/hooks.d.ts +26 -0
  31. package/dist/react-core/hooks.d.ts.map +1 -1
  32. package/dist/react-core/index.js +16 -1
  33. package/dist/react-core/index.js.map +1 -1
  34. package/dist/react-core/testing.d.ts +1 -1
  35. package/dist/react-core/testing.d.ts.map +1 -1
  36. package/dist/react-core/testing.js +3 -1
  37. package/dist/react-core/testing.js.map +1 -1
  38. package/dist/react-core/tests/useSyncConnectionStatus.test.d.ts +2 -0
  39. package/dist/react-core/tests/useSyncConnectionStatus.test.d.ts.map +1 -0
  40. package/dist/react-native-core/ReactNativeContextManager.d.ts +4 -0
  41. package/dist/react-native-core/ReactNativeContextManager.d.ts.map +1 -1
  42. package/dist/react-native-core/hooks.d.ts +1 -1
  43. package/dist/react-native-core/hooks.d.ts.map +1 -1
  44. package/dist/react-native-core/index.js +38 -6
  45. package/dist/react-native-core/index.js.map +1 -1
  46. package/dist/react-native-core/platform.d.ts +4 -0
  47. package/dist/react-native-core/platform.d.ts.map +1 -1
  48. package/dist/svelte/jazz.class.svelte.d.ts +14 -0
  49. package/dist/svelte/jazz.class.svelte.d.ts.map +1 -1
  50. package/dist/svelte/jazz.class.svelte.js +37 -0
  51. package/dist/svelte/testing.d.ts +1 -1
  52. package/dist/svelte/testing.d.ts.map +1 -1
  53. package/dist/svelte/testing.js +1 -1
  54. package/dist/svelte/tests/TestConnectionStatus.svelte +8 -0
  55. package/dist/svelte/tests/TestConnectionStatus.svelte.d.ts +27 -0
  56. package/dist/svelte/tests/TestConnectionStatus.svelte.d.ts.map +1 -0
  57. package/dist/svelte/tests/sync-connection-status.svelte.test.d.ts +2 -0
  58. package/dist/svelte/tests/sync-connection-status.svelte.test.d.ts.map +1 -0
  59. package/dist/svelte/tests/sync-connection-status.svelte.test.js +47 -0
  60. package/dist/testing.js +34 -4
  61. package/dist/testing.js.map +1 -1
  62. package/dist/tools/implementation/ContextManager.d.ts +4 -0
  63. package/dist/tools/implementation/ContextManager.d.ts.map +1 -1
  64. package/dist/tools/subscribe/SubscriptionScope.d.ts.map +1 -1
  65. package/dist/tools/testing.d.ts +8 -0
  66. package/dist/tools/testing.d.ts.map +1 -1
  67. package/dist/tools/types.d.ts +4 -0
  68. package/dist/tools/types.d.ts.map +1 -1
  69. package/package.json +4 -4
  70. package/src/browser/createBrowserContext.ts +34 -4
  71. package/src/react/hooks.tsx +1 -0
  72. package/src/react/index.ts +1 -0
  73. package/src/react-core/hooks.ts +42 -0
  74. package/src/react-core/testing.tsx +1 -0
  75. package/src/react-core/tests/useAccountWithSelector.test.ts +98 -2
  76. package/src/react-core/tests/useSyncConnectionStatus.test.ts +48 -0
  77. package/src/react-native-core/hooks.tsx +1 -0
  78. package/src/react-native-core/platform.ts +32 -4
  79. package/src/svelte/jazz.class.svelte.ts +44 -0
  80. package/src/svelte/testing.ts +1 -0
  81. package/src/svelte/tests/TestConnectionStatus.svelte +8 -0
  82. package/src/svelte/tests/sync-connection-status.svelte.test.ts +61 -0
  83. package/src/tools/implementation/ContextManager.ts +8 -0
  84. package/src/tools/subscribe/SubscriptionScope.ts +5 -1
  85. package/src/tools/testing.ts +29 -0
  86. package/src/tools/tests/ContextManager.test.ts +2 -2
  87. package/src/tools/tests/coMap.test.ts +42 -0
  88. package/src/tools/tests/subscribe.test.ts +1 -4
  89. package/src/tools/types.ts +4 -0
  90. package/dist/chunk-OTWWOZMB.js.map +0 -1
@@ -56,6 +56,8 @@ async function setupPeers(options: BaseReactNativeContextOptions) {
56
56
  if (options.sync.when === "never") {
57
57
  return {
58
58
  toggleNetwork: () => {},
59
+ addConnectionListener: () => () => {},
60
+ connected: () => false,
59
61
  peersToLoadFrom,
60
62
  setNode: () => {},
61
63
  crypto,
@@ -96,6 +98,14 @@ async function setupPeers(options: BaseReactNativeContextOptions) {
96
98
 
97
99
  return {
98
100
  toggleNetwork,
101
+ addConnectionListener(listener: (connected: boolean) => void) {
102
+ wsPeer.subscribe(listener);
103
+
104
+ return () => {
105
+ wsPeer.unsubscribe(listener);
106
+ };
107
+ },
108
+ connected: () => wsPeer.connected,
99
109
  peersToLoadFrom,
100
110
  setNode,
101
111
  crypto,
@@ -106,8 +116,15 @@ async function setupPeers(options: BaseReactNativeContextOptions) {
106
116
  export async function createJazzReactNativeGuestContext(
107
117
  options: BaseReactNativeContextOptions,
108
118
  ) {
109
- const { toggleNetwork, peersToLoadFrom, setNode, crypto, storage } =
110
- await setupPeers(options);
119
+ const {
120
+ toggleNetwork,
121
+ peersToLoadFrom,
122
+ setNode,
123
+ crypto,
124
+ storage,
125
+ addConnectionListener,
126
+ connected,
127
+ } = await setupPeers(options);
111
128
 
112
129
  const context = createAnonymousJazzContext({
113
130
  crypto,
@@ -130,6 +147,8 @@ export async function createJazzReactNativeGuestContext(
130
147
  logOut: () => {
131
148
  return context.logOut();
132
149
  },
150
+ addConnectionListener,
151
+ connected,
133
152
  };
134
153
  }
135
154
 
@@ -149,8 +168,15 @@ export async function createJazzReactNativeContext<
149
168
  | (AccountClass<Account> & CoValueFromRaw<Account>)
150
169
  | AnyAccountSchema,
151
170
  >(options: ReactNativeContextOptions<S>) {
152
- const { toggleNetwork, peersToLoadFrom, setNode, crypto, storage } =
153
- await setupPeers(options);
171
+ const {
172
+ toggleNetwork,
173
+ peersToLoadFrom,
174
+ setNode,
175
+ crypto,
176
+ storage,
177
+ addConnectionListener,
178
+ connected,
179
+ } = await setupPeers(options);
154
180
 
155
181
  let unsubscribeAuthUpdate = () => {};
156
182
 
@@ -201,6 +227,8 @@ export async function createJazzReactNativeContext<
201
227
  unsubscribeAuthUpdate();
202
228
  return context.logOut();
203
229
  },
230
+ addConnectionListener,
231
+ connected,
204
232
  };
205
233
  }
206
234
 
@@ -205,3 +205,47 @@ export class AccountCoState<
205
205
  return this.#isAuthenticated.current;
206
206
  }
207
207
  }
208
+
209
+ /**
210
+ * Class that provides the current connection status to the Jazz sync server.
211
+ *
212
+ * @returns `true` when connected to the server, `false` when disconnected
213
+ *
214
+ * @remarks
215
+ * On connection drop, this will return `false` only when Jazz detects the disconnection
216
+ * after 5 seconds of not receiving a ping from the server.
217
+ */
218
+ export class SyncConnectionStatus {
219
+ #ctx = getJazzContext<InstanceOfSchema<AccountClass<Account>>>();
220
+ #subscribe: () => void;
221
+ #update = () => {};
222
+
223
+ constructor() {
224
+ this.#subscribe = createSubscriber((update) => {
225
+ this.#update = update;
226
+ });
227
+
228
+ $effect.pre(() => {
229
+ const ctx = this.#ctx.current;
230
+
231
+ return untrack(() => {
232
+ if (!ctx) {
233
+ return;
234
+ }
235
+
236
+ const unsubscribe = ctx.addConnectionListener(() => {
237
+ this.#update();
238
+ });
239
+
240
+ return () => {
241
+ unsubscribe();
242
+ };
243
+ });
244
+ });
245
+ }
246
+
247
+ get current() {
248
+ this.#subscribe();
249
+ return this.#ctx.current?.connected() ?? false;
250
+ }
251
+ }
@@ -39,4 +39,5 @@ export {
39
39
  linkAccounts,
40
40
  setActiveAccount,
41
41
  setupJazzTestSync,
42
+ MockConnectionStatus,
42
43
  } from "jazz-tools/testing";
@@ -0,0 +1,8 @@
1
+ <script>
2
+ import { SyncConnectionStatus } from "../jazz.class.svelte";
3
+ const connectionStatus = new SyncConnectionStatus();
4
+ </script>
5
+
6
+ <div>
7
+ <div data-testid="connected">{connectionStatus.current ? "true" : "false"}</div>
8
+ </div>
@@ -0,0 +1,61 @@
1
+ // @vitest-environment happy-dom
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
+ import {
4
+ createJazzTestAccount,
5
+ setupJazzTestSync,
6
+ MockConnectionStatus,
7
+ } from "../testing";
8
+ import { render, screen, waitFor } from "./testUtils";
9
+ import TestConnectionStatus from "./TestConnectionStatus.svelte";
10
+
11
+ describe("SyncConnectionStatus", () => {
12
+ beforeEach(async () => {
13
+ await setupJazzTestSync();
14
+ await createJazzTestAccount({
15
+ isCurrentActiveAccount: true,
16
+ });
17
+ });
18
+
19
+ afterEach(() => {
20
+ vi.clearAllMocks();
21
+ });
22
+
23
+ it("should return true by default in the test environment", async () => {
24
+ const { container } = render(TestConnectionStatus, {}, {
25
+ account: await createJazzTestAccount({
26
+ isCurrentActiveAccount: true,
27
+ }),
28
+ });
29
+
30
+ await waitFor(() => {
31
+ expect(screen.getByTestId("connected").textContent).toBe("true");
32
+ });
33
+ });
34
+
35
+ it("should handle updates", async () => {
36
+ const { container } = render(TestConnectionStatus, {}, {
37
+ account: await createJazzTestAccount({
38
+ isCurrentActiveAccount: true,
39
+ }),
40
+ });
41
+
42
+ // Initially should be connected
43
+ await waitFor(() => {
44
+ expect(screen.getByTestId("connected").textContent).toBe("true");
45
+ });
46
+
47
+ // Simulate disconnection
48
+ MockConnectionStatus.setIsConnected(false);
49
+
50
+ await waitFor(() => {
51
+ expect(screen.getByTestId("connected").textContent).toBe("false");
52
+ });
53
+
54
+ // Simulate reconnection
55
+ MockConnectionStatus.setIsConnected(true);
56
+
57
+ await waitFor(() => {
58
+ expect(screen.getByTestId("connected").textContent).toBe("true");
59
+ });
60
+ });
61
+ });
@@ -26,6 +26,8 @@ type PlatformSpecificAuthContext<Acc extends Account> = {
26
26
  node: LocalNode;
27
27
  logOut: () => Promise<void>;
28
28
  done: () => void;
29
+ addConnectionListener: (listener: (connected: boolean) => void) => () => void;
30
+ connected: () => boolean;
29
31
  };
30
32
 
31
33
  type PlatformSpecificGuestContext = {
@@ -33,6 +35,8 @@ type PlatformSpecificGuestContext = {
33
35
  node: LocalNode;
34
36
  logOut: () => Promise<void>;
35
37
  done: () => void;
38
+ addConnectionListener: (listener: (connected: boolean) => void) => () => void;
39
+ connected: () => boolean;
36
40
  };
37
41
 
38
42
  type PlatformSpecificContext<Acc extends Account> =
@@ -52,6 +56,8 @@ function getAnonymousFallback() {
52
56
  logOut: async () => {},
53
57
  isAuthenticated: false,
54
58
  authenticate: async () => {},
59
+ addConnectionListener: () => () => {},
60
+ connected: () => false,
55
61
  register: async () => {
56
62
  throw new Error("Not implemented");
57
63
  },
@@ -134,6 +140,8 @@ export class JazzContextManager<
134
140
  authenticate: this.authenticate,
135
141
  register: this.register,
136
142
  logOut: this.logOut,
143
+ addConnectionListener: context.addConnectionListener,
144
+ connected: context.connected,
137
145
  };
138
146
 
139
147
  if (authProps?.credentials) {
@@ -79,7 +79,11 @@ export class SubscriptionScope<D extends CoValue> {
79
79
  // - Run the migration only once
80
80
  // - Skip all the updates until the migration is done
81
81
  // - Trigger handleUpdate only with the final value
82
- if (!this.migrated && value !== "unavailable") {
82
+ if (
83
+ !this.migrated &&
84
+ value !== "unavailable" &&
85
+ !value.core.verified.isStreaming()
86
+ ) {
83
87
  if (this.migrating) {
84
88
  return;
85
89
  }
@@ -193,6 +193,23 @@ export async function createJazzTestGuest() {
193
193
  };
194
194
  }
195
195
 
196
+ export class MockConnectionStatus {
197
+ static connected: boolean = true;
198
+ static connectionListeners = new Set<(isConnected: boolean) => void>();
199
+ static setIsConnected(isConnected: boolean) {
200
+ MockConnectionStatus.connected = isConnected;
201
+ for (const listener of MockConnectionStatus.connectionListeners) {
202
+ listener(isConnected);
203
+ }
204
+ }
205
+ static addConnectionListener(listener: (isConnected: boolean) => void) {
206
+ MockConnectionStatus.connectionListeners.add(listener);
207
+ return () => {
208
+ MockConnectionStatus.connectionListeners.delete(listener);
209
+ };
210
+ }
211
+ }
212
+
196
213
  export type TestJazzContextManagerProps<Acc extends Account> =
197
214
  JazzContextManagerBaseProps<Acc> & {
198
215
  defaultProfileName?: string;
@@ -249,6 +266,10 @@ export class TestJazzContextManager<
249
266
  await storage.clear();
250
267
  node.gracefulShutdown();
251
268
  },
269
+ addConnectionListener: (listener) => {
270
+ return MockConnectionStatus.addConnectionListener(listener);
271
+ },
272
+ connected: () => MockConnectionStatus.connected,
252
273
  },
253
274
  {
254
275
  credentials,
@@ -274,6 +295,10 @@ export class TestJazzContextManager<
274
295
  logOut: async () => {
275
296
  node.gracefulShutdown();
276
297
  },
298
+ addConnectionListener: (listener) => {
299
+ return MockConnectionStatus.addConnectionListener(listener);
300
+ },
301
+ connected: () => MockConnectionStatus.connected,
277
302
  });
278
303
 
279
304
  return context;
@@ -309,6 +334,10 @@ export class TestJazzContextManager<
309
334
  logOut: () => {
310
335
  return context.logOut();
311
336
  },
337
+ addConnectionListener: (listener: (isConnected: boolean) => void) => {
338
+ return MockConnectionStatus.addConnectionListener(listener);
339
+ },
340
+ connected: () => MockConnectionStatus.connected,
312
341
  };
313
342
  }
314
343
  }
@@ -73,6 +73,8 @@ class TestJazzContextManager<Acc extends Account> extends JazzContextManager<
73
73
  logOut: async () => {
74
74
  await context.logOut();
75
75
  },
76
+ addConnectionListener: () => () => {},
77
+ connected: () => false,
76
78
  };
77
79
  }
78
80
  }
@@ -80,14 +82,12 @@ class TestJazzContextManager<Acc extends Account> extends JazzContextManager<
80
82
  describe("ContextManager", () => {
81
83
  let manager: TestJazzContextManager<Account>;
82
84
  let authSecretStorage: AuthSecretStorage;
83
- let storage: StorageAPI;
84
85
 
85
86
  function getCurrentValue() {
86
87
  return manager.getCurrentValue() as JazzAuthContext<Account>;
87
88
  }
88
89
 
89
90
  beforeEach(async () => {
90
- storage = await createAsyncStorage({});
91
91
  KvStoreContext.getInstance().initialize(new InMemoryKVStore());
92
92
  authSecretStorage = new AuthSecretStorage();
93
93
  await authSecretStorage.clear();
@@ -2510,6 +2510,48 @@ describe("CoMap migration", () => {
2510
2510
  expect(spy).toHaveBeenCalledTimes(1);
2511
2511
  });
2512
2512
 
2513
+ test("should run only when the value is fully loaded", async () => {
2514
+ await setupJazzTestSync({
2515
+ asyncPeers: true,
2516
+ });
2517
+ await createJazzTestAccount({
2518
+ isCurrentActiveAccount: true,
2519
+ creationProps: { name: "Hermes Puggington" },
2520
+ });
2521
+
2522
+ const migration = vi.fn();
2523
+ const Person = co
2524
+ .map({
2525
+ name: z.string(),
2526
+ update: z.number(),
2527
+ })
2528
+ .withMigration((person) => {
2529
+ migration(person.update);
2530
+ });
2531
+
2532
+ const person = Person.create({
2533
+ name: "Bob",
2534
+ update: 1,
2535
+ });
2536
+
2537
+ // Pump the value to reach streaming
2538
+ for (let i = 0; i <= 300; i++) {
2539
+ person.$jazz.raw.assign({
2540
+ name: "1".repeat(1024),
2541
+ update: i,
2542
+ });
2543
+ }
2544
+
2545
+ // Upload and unmount, to force the streaming download
2546
+ await person.$jazz.waitForSync();
2547
+ person.$jazz.raw.core.unmount();
2548
+
2549
+ // Load the value and expect the migration to run only once
2550
+ await Person.load(person.$jazz.id);
2551
+ expect(migration).toHaveBeenCalledTimes(1);
2552
+ expect(migration).toHaveBeenCalledWith(300);
2553
+ });
2554
+
2513
2555
  test("should not break recursive schemas", async () => {
2514
2556
  const PersonV1 = co.map({
2515
2557
  name: z.string(),
@@ -559,10 +559,7 @@ describe("subscribeToCoValue", () => {
559
559
  assert(result);
560
560
 
561
561
  expect(result[0]?.value).toBe("1");
562
-
563
- // expect(updateFn).toHaveBeenCalledTimes(1);
564
- // TODO: Getting an extra update here due to https://github.com/garden-co/jazz/issues/2117
565
- expect(updateFn).toHaveBeenCalledTimes(2);
562
+ expect(updateFn).toHaveBeenCalledTimes(1);
566
563
  });
567
564
 
568
565
  it("should handle undefined values in lists with required refs", async () => {
@@ -26,6 +26,8 @@ export type JazzAuthContext<Acc extends Account> = {
26
26
  logOut: () => Promise<void>;
27
27
  done: () => void;
28
28
  isAuthenticated?: boolean;
29
+ addConnectionListener: (listener: (connected: boolean) => void) => () => void;
30
+ connected: () => boolean;
29
31
  };
30
32
 
31
33
  export type JazzGuestContext = {
@@ -36,6 +38,8 @@ export type JazzGuestContext = {
36
38
  logOut: () => void;
37
39
  done: () => void;
38
40
  isAuthenticated?: boolean;
41
+ addConnectionListener: (listener: (connected: boolean) => void) => () => void;
42
+ connected: () => boolean;
39
43
  };
40
44
 
41
45
  export type JazzContextType<Acc extends Account> =