jazz-tools 0.19.21 → 0.20.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.
Files changed (223) hide show
  1. package/.svelte-kit/__package__/react.d.ts.map +1 -1
  2. package/.svelte-kit/__package__/react.tsx +5 -2
  3. package/.turbo/turbo-build.log +77 -89
  4. package/CHANGELOG.md +84 -0
  5. package/dist/better-auth/auth/react.d.ts.map +1 -1
  6. package/dist/better-auth/auth/react.js +5 -2
  7. package/dist/better-auth/auth/react.js.map +1 -1
  8. package/dist/browser/createBrowserContext.d.ts +1 -2
  9. package/dist/browser/createBrowserContext.d.ts.map +1 -1
  10. package/dist/browser/index.js +1 -8
  11. package/dist/browser/index.js.map +1 -1
  12. package/dist/browser/provideBrowserLockSession/SessionIDStorage.d.ts +1 -1
  13. package/dist/{chunk-QCTQH5RS.js → chunk-3CAPPS2F.js} +234 -101
  14. package/dist/chunk-3CAPPS2F.js.map +1 -0
  15. package/dist/{chunk-M2HGBOXS.js → chunk-K4D7IMFM.js} +3 -3
  16. package/dist/chunk-K4D7IMFM.js.map +1 -0
  17. package/dist/expo/auth/clerk/index.d.ts.map +1 -1
  18. package/dist/expo/index.js +5 -2
  19. package/dist/expo/index.js.map +1 -1
  20. package/dist/index.js +39 -2
  21. package/dist/index.js.map +1 -1
  22. package/dist/inspector/{chunk-YQNK5Y7B.js → chunk-MCTB5ZJC.js} +1 -1
  23. package/dist/inspector/chunk-MCTB5ZJC.js.map +1 -0
  24. package/dist/inspector/contexts/node.d.ts.map +1 -1
  25. package/dist/inspector/{custom-element-KYV64IOC.js → custom-element-5YWVZBWA.js} +1 -1
  26. package/dist/inspector/{custom-element-KYV64IOC.js.map → custom-element-5YWVZBWA.js.map} +1 -1
  27. package/dist/inspector/index.js +3 -3
  28. package/dist/inspector/index.js.map +1 -1
  29. package/dist/inspector/register-custom-element.js +1 -1
  30. package/dist/inspector/standalone.js +1 -1
  31. package/dist/media/{chunk-3LKBM3G3.js → chunk-IRL3KNPO.js} +2 -2
  32. package/dist/media/{chunk-3LKBM3G3.js.map → chunk-IRL3KNPO.js.map} +1 -1
  33. package/dist/media/create-image/react-native.d.ts +1 -1
  34. package/dist/media/create-image/react-native.d.ts.map +1 -1
  35. package/dist/media/index.browser.js +1 -1
  36. package/dist/media/index.js +1 -1
  37. package/dist/media/index.native.js +5 -5
  38. package/dist/media/index.native.js.map +1 -1
  39. package/dist/media/index.server.js +1 -1
  40. package/dist/react/auth/Clerk.d.ts.map +1 -1
  41. package/dist/react/hooks.d.ts +1 -1
  42. package/dist/react/hooks.d.ts.map +1 -1
  43. package/dist/react/index.js +61 -47
  44. package/dist/react/index.js.map +1 -1
  45. package/dist/react/provider.d.ts.map +1 -1
  46. package/dist/react/ssr.js +2 -2
  47. package/dist/react/ssr.js.map +1 -1
  48. package/dist/react-core/chunk-UOYH6JFJ.js +10 -0
  49. package/dist/react-core/chunk-UOYH6JFJ.js.map +1 -0
  50. package/dist/react-core/hooks.d.ts +3 -3
  51. package/dist/react-core/hooks.d.ts.map +1 -1
  52. package/dist/react-core/index.js +27 -25
  53. package/dist/react-core/index.js.map +1 -1
  54. package/dist/react-core/provider.d.ts +2 -3
  55. package/dist/react-core/provider.d.ts.map +1 -1
  56. package/dist/react-core/testing.d.ts.map +1 -1
  57. package/dist/react-core/testing.js +4 -10
  58. package/dist/react-core/testing.js.map +1 -1
  59. package/dist/react-native/index.js +61 -53
  60. package/dist/react-native/index.js.map +1 -1
  61. package/dist/react-native-core/ReactNativeContextManager.d.ts +0 -1
  62. package/dist/react-native-core/ReactNativeContextManager.d.ts.map +1 -1
  63. package/dist/react-native-core/hooks.d.ts +1 -1
  64. package/dist/react-native-core/hooks.d.ts.map +1 -1
  65. package/dist/react-native-core/index.js +58 -50
  66. package/dist/react-native-core/index.js.map +1 -1
  67. package/dist/react-native-core/platform.d.ts +0 -4
  68. package/dist/react-native-core/platform.d.ts.map +1 -1
  69. package/dist/react-native-core/provider.d.ts +2 -1
  70. package/dist/react-native-core/provider.d.ts.map +1 -1
  71. package/dist/svelte/jazz.class.svelte.d.ts.map +1 -1
  72. package/dist/svelte/jazz.class.svelte.js +2 -8
  73. package/dist/svelte/tests/AccountCoState.svelte.test.d.ts +2 -0
  74. package/dist/svelte/tests/AccountCoState.svelte.test.d.ts.map +1 -0
  75. package/dist/svelte/tests/AccountCoState.svelte.test.js +59 -0
  76. package/dist/svelte/tests/CoState.svelte.test.js +23 -0
  77. package/dist/svelte/tests/TestAccountCoStateWrapper.svelte +24 -0
  78. package/dist/svelte/tests/TestAccountCoStateWrapper.svelte.d.ts +11 -0
  79. package/dist/svelte/tests/TestAccountCoStateWrapper.svelte.d.ts.map +1 -0
  80. package/dist/testing.js +6 -6
  81. package/dist/testing.js.map +1 -1
  82. package/dist/tools/coValues/coList.d.ts +2 -2
  83. package/dist/tools/coValues/coList.d.ts.map +1 -1
  84. package/dist/tools/coValues/coMap.d.ts +2 -2
  85. package/dist/tools/coValues/deepLoading.d.ts +2 -2
  86. package/dist/tools/coValues/deepLoading.d.ts.map +1 -1
  87. package/dist/tools/coValues/interfaces.d.ts +32 -0
  88. package/dist/tools/coValues/interfaces.d.ts.map +1 -1
  89. package/dist/tools/exports.d.ts +2 -1
  90. package/dist/tools/exports.d.ts.map +1 -1
  91. package/dist/tools/implementation/ContextManager.d.ts.map +1 -1
  92. package/dist/tools/implementation/createContext.d.ts.map +1 -1
  93. package/dist/tools/implementation/zodSchema/schemaTypes/AccountSchema.d.ts +3 -2
  94. package/dist/tools/implementation/zodSchema/schemaTypes/AccountSchema.d.ts.map +1 -1
  95. package/dist/tools/implementation/zodSchema/schemaTypes/CoDiscriminatedUnionSchema.d.ts +3 -2
  96. package/dist/tools/implementation/zodSchema/schemaTypes/CoDiscriminatedUnionSchema.d.ts.map +1 -1
  97. package/dist/tools/implementation/zodSchema/schemaTypes/CoFeedSchema.d.ts +3 -3
  98. package/dist/tools/implementation/zodSchema/schemaTypes/CoFeedSchema.d.ts.map +1 -1
  99. package/dist/tools/implementation/zodSchema/schemaTypes/CoListSchema.d.ts +3 -2
  100. package/dist/tools/implementation/zodSchema/schemaTypes/CoListSchema.d.ts.map +1 -1
  101. package/dist/tools/implementation/zodSchema/schemaTypes/CoMapSchema.d.ts +5 -2
  102. package/dist/tools/implementation/zodSchema/schemaTypes/CoMapSchema.d.ts.map +1 -1
  103. package/dist/tools/implementation/zodSchema/zodCo.d.ts.map +1 -1
  104. package/dist/tools/ssr.js +1 -1
  105. package/dist/tools/subscribe/JazzError.d.ts +3 -3
  106. package/dist/tools/subscribe/JazzError.d.ts.map +1 -1
  107. package/dist/tools/subscribe/SubscriptionScope.d.ts.map +1 -1
  108. package/dist/tools/subscribe/types.d.ts +5 -1
  109. package/dist/tools/subscribe/types.d.ts.map +1 -1
  110. package/dist/tools/testing.d.ts +3 -3
  111. package/dist/tools/testing.d.ts.map +1 -1
  112. package/dist/tools/tests/deleteCoValues.test.d.ts +2 -0
  113. package/dist/tools/tests/deleteCoValues.test.d.ts.map +1 -0
  114. package/dist/tools/tests/deletedState.test.d.ts +2 -0
  115. package/dist/tools/tests/deletedState.test.d.ts.map +1 -0
  116. package/dist/worker/JazzMessageChannel.d.ts +36 -0
  117. package/dist/worker/JazzMessageChannel.d.ts.map +1 -0
  118. package/dist/worker/edge-wasm.js +2 -1
  119. package/dist/worker/edge-wasm.js.map +1 -1
  120. package/dist/worker/index.d.ts +7 -1
  121. package/dist/worker/index.d.ts.map +1 -1
  122. package/dist/worker/index.js +28 -17
  123. package/dist/worker/index.js.map +1 -1
  124. package/dist/worker/wasm.d.ts +2 -0
  125. package/dist/worker/wasm.d.ts.map +1 -0
  126. package/package.json +9 -28
  127. package/src/better-auth/auth/react.tsx +5 -2
  128. package/src/browser/createBrowserContext.ts +2 -5
  129. package/src/expo/auth/clerk/index.tsx +5 -2
  130. package/src/inspector/contexts/node.tsx +1 -2
  131. package/src/inspector/index.tsx +2 -2
  132. package/src/media/create-image/react-native.ts +9 -7
  133. package/src/media/create-image-factory.test.ts +1 -1
  134. package/src/media/create-image-factory.ts +1 -1
  135. package/src/react/auth/Clerk.tsx +5 -2
  136. package/src/react/auth/PasskeyAuth.tsx +2 -2
  137. package/src/react/hooks.tsx +3 -2
  138. package/src/react/provider.tsx +45 -41
  139. package/src/react-core/auth/DemoAuth.tsx +2 -2
  140. package/src/react-core/auth/PassphraseAuth.tsx +2 -2
  141. package/src/react-core/hooks.ts +26 -27
  142. package/src/react-core/provider.tsx +1 -5
  143. package/src/react-core/testing.tsx +3 -11
  144. package/src/react-core/tests/testUtils.tsx +2 -2
  145. package/src/react-core/tests/useAccount.selector.test.ts +2 -3
  146. package/src/react-core/tests/useAccount.test.ts +57 -7
  147. package/src/react-core/tests/useCoState.test.ts +37 -0
  148. package/src/react-core/tests/useInboxSender.test.ts +2 -5
  149. package/src/react-core/tests/useSuspenseAccount.test.tsx +68 -0
  150. package/src/react-core/tests/useSuspenseCoState.test.tsx +44 -0
  151. package/src/react-native-core/ReactNativeContextManager.ts +0 -3
  152. package/src/react-native-core/auth/usePasskeyAuth.tsx +2 -2
  153. package/src/react-native-core/hooks.tsx +3 -3
  154. package/src/react-native-core/platform.ts +2 -6
  155. package/src/react-native-core/provider.tsx +47 -43
  156. package/src/svelte/jazz.class.svelte.ts +2 -8
  157. package/src/svelte/tests/AccountCoState.svelte.test.ts +79 -0
  158. package/src/svelte/tests/CoState.svelte.test.ts +36 -0
  159. package/src/svelte/tests/TestAccountCoStateWrapper.svelte +24 -0
  160. package/src/tools/coValues/deepLoading.ts +2 -0
  161. package/src/tools/coValues/interfaces.ts +170 -32
  162. package/src/tools/exports.ts +6 -0
  163. package/src/tools/implementation/ContextManager.ts +2 -2
  164. package/src/tools/implementation/createContext.ts +4 -0
  165. package/src/tools/implementation/zodSchema/schemaTypes/AccountSchema.ts +30 -6
  166. package/src/tools/implementation/zodSchema/schemaTypes/CoDiscriminatedUnionSchema.ts +55 -7
  167. package/src/tools/implementation/zodSchema/schemaTypes/CoFeedSchema.ts +33 -14
  168. package/src/tools/implementation/zodSchema/schemaTypes/CoListSchema.ts +35 -6
  169. package/src/tools/implementation/zodSchema/schemaTypes/CoMapSchema.ts +35 -14
  170. package/src/tools/ssr/ssr.ts +2 -2
  171. package/src/tools/subscribe/CoValueCoreSubscription.ts +1 -0
  172. package/src/tools/subscribe/JazzError.ts +4 -1
  173. package/src/tools/subscribe/SubscriptionScope.ts +23 -0
  174. package/src/tools/subscribe/types.ts +5 -0
  175. package/src/tools/testing.ts +5 -5
  176. package/src/tools/tests/PassphraseAuth.test.ts +5 -5
  177. package/src/tools/tests/deleteCoValues.test.ts +231 -0
  178. package/src/tools/tests/deletedState.test.ts +110 -0
  179. package/src/tools/tests/request.test.ts +15 -2
  180. package/src/tools/tests/testStorage.ts +2 -2
  181. package/src/worker/JazzMessageChannel.ts +73 -0
  182. package/src/worker/edge-wasm.ts +2 -1
  183. package/src/worker/index.ts +36 -17
  184. package/src/worker/wasm.ts +1 -0
  185. package/tsup.config.ts +0 -4
  186. package/dist/browser/storageOptions.d.ts +0 -8
  187. package/dist/browser/storageOptions.d.ts.map +0 -1
  188. package/dist/browser/tests/storageOptions.test.d.ts +0 -2
  189. package/dist/browser/tests/storageOptions.test.d.ts.map +0 -1
  190. package/dist/chunk-M2HGBOXS.js.map +0 -1
  191. package/dist/chunk-QCTQH5RS.js.map +0 -1
  192. package/dist/expo/crypto.d.ts +0 -2
  193. package/dist/expo/crypto.d.ts.map +0 -1
  194. package/dist/expo/crypto.js +0 -6
  195. package/dist/expo/crypto.js.map +0 -1
  196. package/dist/inspector/chunk-YQNK5Y7B.js.map +0 -1
  197. package/dist/react-core/chunk-7DYMJ74I.js +0 -12
  198. package/dist/react-core/chunk-7DYMJ74I.js.map +0 -1
  199. package/dist/react-native/chunk-DGUM43GV.js +0 -11
  200. package/dist/react-native/chunk-DGUM43GV.js.map +0 -1
  201. package/dist/react-native/crypto.d.ts +0 -2
  202. package/dist/react-native/crypto.d.ts.map +0 -1
  203. package/dist/react-native/crypto.js +0 -8
  204. package/dist/react-native/crypto.js.map +0 -1
  205. package/dist/react-native-core/chunk-DGUM43GV.js +0 -11
  206. package/dist/react-native-core/chunk-DGUM43GV.js.map +0 -1
  207. package/dist/react-native-core/crypto/RNCrypto.d.ts +0 -2
  208. package/dist/react-native-core/crypto/RNCrypto.d.ts.map +0 -1
  209. package/dist/react-native-core/crypto/RNCrypto.js +0 -3
  210. package/dist/react-native-core/crypto/RNCrypto.js.map +0 -1
  211. package/dist/react-native-core/crypto/RNQuickCrypto.d.ts +0 -17
  212. package/dist/react-native-core/crypto/RNQuickCrypto.d.ts.map +0 -1
  213. package/dist/react-native-core/crypto/index.d.ts +0 -2
  214. package/dist/react-native-core/crypto/index.d.ts.map +0 -1
  215. package/dist/react-native-core/crypto.js +0 -89
  216. package/dist/react-native-core/crypto.js.map +0 -1
  217. package/src/browser/storageOptions.ts +0 -17
  218. package/src/browser/tests/storageOptions.test.ts +0 -33
  219. package/src/expo/crypto.ts +0 -1
  220. package/src/react-native/crypto.ts +0 -1
  221. package/src/react-native-core/crypto/RNCrypto.ts +0 -1
  222. package/src/react-native-core/crypto/RNQuickCrypto.ts +0 -122
  223. package/src/react-native-core/crypto/index.ts +0 -1
@@ -111,8 +111,8 @@ export async function createAsyncStorage() {
111
111
  new LibSQLSqliteAsyncDriver(":memory:"),
112
112
  );
113
113
 
114
- onTestFinished(() => {
115
- storage.close();
114
+ onTestFinished(async () => {
115
+ await storage.close();
116
116
  });
117
117
 
118
118
  return storage;
@@ -1,9 +1,8 @@
1
1
  // @vitest-environment happy-dom
2
2
 
3
- import { Account, RefsToResolve, co, z, Group } from "jazz-tools";
3
+ import { Account, co, z, Group } from "jazz-tools";
4
4
  import { assert, beforeEach, describe, expect, it } from "vitest";
5
- import { useAccount, useJazzContextManager } from "../hooks.js";
6
- import { useIsAuthenticated } from "../index.js";
5
+ import { useAccount } from "../hooks.js";
7
6
  import {
8
7
  createJazzTestAccount,
9
8
  createJazzTestGuest,
@@ -3,12 +3,7 @@
3
3
  import { CoValueLoadingState, Group, RefsToResolve, co, z } from "jazz-tools";
4
4
  import { assertLoaded } from "jazz-tools/testing";
5
5
  import { assert, beforeEach, describe, expect, it } from "vitest";
6
- import {
7
- useAccount,
8
- useAgent,
9
- useJazzContextManager,
10
- useLogOut,
11
- } from "../hooks.js";
6
+ import { useAccount, useAgent, useJazzContext, useLogOut } from "../hooks.js";
12
7
  import { useIsAuthenticated } from "../index.js";
13
8
  import {
14
9
  createJazzTestAccount,
@@ -68,6 +63,61 @@ describe("useAccount", () => {
68
63
  expect(result.current.root.value).toBe("123");
69
64
  });
70
65
 
66
+ it("should return a 'deleted' value when a required resolved child is deleted", async () => {
67
+ const AccountRoot = co.map({
68
+ value: z.string(),
69
+ });
70
+
71
+ const AccountSchema = co
72
+ .account({
73
+ root: AccountRoot,
74
+ profile: co.profile(),
75
+ })
76
+ .withMigration((account) => {
77
+ if (!account.$jazz.refs.root) {
78
+ account.$jazz.set("root", { value: "123" });
79
+ }
80
+ });
81
+
82
+ const account = await createJazzTestAccount({
83
+ AccountSchema,
84
+ isCurrentActiveAccount: true,
85
+ });
86
+
87
+ const { result } = renderHook(
88
+ () =>
89
+ useAccount(AccountSchema, {
90
+ resolve: {
91
+ root: true,
92
+ },
93
+ }),
94
+ {
95
+ account,
96
+ },
97
+ );
98
+
99
+ await waitFor(() => {
100
+ assertLoaded(result.current);
101
+ assertLoaded(result.current.root);
102
+ expect(result.current.root.value).toBe("123");
103
+ });
104
+
105
+ assertLoaded(result.current);
106
+ assertLoaded(result.current.root);
107
+ // Capture the root reference before deletion; after deletion the account may become NotLoaded.
108
+ const root = result.current.root;
109
+
110
+ act(() => {
111
+ root.$jazz.raw.core.deleteCoValue();
112
+ });
113
+
114
+ await waitFor(() => {
115
+ expect(result.current.$jazz.loadingState).toBe(
116
+ CoValueLoadingState.DELETED,
117
+ );
118
+ });
119
+ });
120
+
71
121
  it("should be in sync with useIsAuthenticated when logOut is called", async () => {
72
122
  const account = await createJazzTestAccount({});
73
123
 
@@ -140,7 +190,7 @@ describe("useAccount", () => {
140
190
  () => {
141
191
  const isAuthenticated = useIsAuthenticated();
142
192
  const account = useAccount();
143
- const contextManager = useJazzContextManager();
193
+ const contextManager = useJazzContext();
144
194
 
145
195
  if (account) {
146
196
  if (!accounts.includes(account.$jazz.id)) {
@@ -224,6 +224,43 @@ describe("useCoState", () => {
224
224
  });
225
225
  });
226
226
 
227
+ it("should return a 'deleted' value when the coValue is deleted", async () => {
228
+ const TestMap = co.map({
229
+ value: z.string(),
230
+ });
231
+
232
+ const account = await createJazzTestAccount({
233
+ isCurrentActiveAccount: true,
234
+ });
235
+
236
+ const map = TestMap.create(
237
+ {
238
+ value: "123",
239
+ },
240
+ Group.create(account).makePublic("reader"),
241
+ );
242
+
243
+ const { result } = renderHook(() => useCoState(TestMap, map.$jazz.id), {
244
+ account,
245
+ });
246
+
247
+ await waitFor(() => {
248
+ assertLoaded(result.current);
249
+ expect(result.current.value).toBe("123");
250
+ });
251
+
252
+ console.log("deleting");
253
+ act(() => {
254
+ map.$jazz.raw.core.deleteCoValue();
255
+ });
256
+
257
+ await waitFor(() => {
258
+ expect(result.current.$jazz.loadingState).toBe(
259
+ CoValueLoadingState.DELETED,
260
+ );
261
+ });
262
+ });
263
+
227
264
  it("should return a 'loaded' value if the coValue is shared with everyone", async () => {
228
265
  const TestMap = co.map({
229
266
  value: z.string(),
@@ -3,10 +3,7 @@
3
3
  import { CoMap, Group, Inbox, Loaded, co, z } from "jazz-tools";
4
4
  import { assertLoaded } from "jazz-tools/testing";
5
5
  import { describe, expect, it } from "vitest";
6
- import {
7
- experimental_useInboxSender,
8
- useJazzContextManager,
9
- } from "../index.js";
6
+ import { experimental_useInboxSender, useJazzContext } from "../index.js";
10
7
  import {
11
8
  createJazzTestAccount,
12
9
  linkAccounts,
@@ -75,7 +72,7 @@ describe("useInboxSender", () => {
75
72
 
76
73
  const { result } = renderHook(
77
74
  () => {
78
- const ctx = useJazzContextManager();
75
+ const ctx = useJazzContext();
79
76
  const send = experimental_useInboxSender(inboxReceiver.$jazz.id);
80
77
  return { ctx, send };
81
78
  },
@@ -239,6 +239,74 @@ describe("useSuspenseAccount", () => {
239
239
  });
240
240
  });
241
241
 
242
+ it("should throw error when a required resolved child is deleted", async () => {
243
+ const AccountRoot = co.map({
244
+ value: z.string(),
245
+ });
246
+
247
+ const MyAppAccount = co
248
+ .account({
249
+ profile: co.profile({
250
+ name: z.string(),
251
+ }),
252
+ root: AccountRoot,
253
+ })
254
+ .withMigration((account, creationProps) => {
255
+ if (!account.$jazz.refs.profile) {
256
+ account.$jazz.set("profile", {
257
+ name: creationProps?.name || "John Doe",
258
+ });
259
+ }
260
+ if (!account.$jazz.refs.root) {
261
+ account.$jazz.set("root", {
262
+ value: "123",
263
+ });
264
+ }
265
+ });
266
+
267
+ const account = await createJazzTestAccount({
268
+ AccountSchema: MyAppAccount,
269
+ isCurrentActiveAccount: true,
270
+ creationProps: {
271
+ name: "John Doe",
272
+ },
273
+ });
274
+
275
+ // Ensure root exists, then delete it.
276
+ const loaded = await account.$jazz.ensureLoaded({
277
+ resolve: { root: true },
278
+ });
279
+ loaded.root.$jazz.raw.core.deleteCoValue();
280
+
281
+ const TestComponent = () => {
282
+ const account = useSuspenseAccount(MyAppAccount, {
283
+ resolve: {
284
+ root: true,
285
+ profile: true,
286
+ },
287
+ });
288
+ return <div>{account.profile.name}</div>;
289
+ };
290
+
291
+ const { container } = await act(async () => {
292
+ return render(
293
+ <ErrorBoundary FallbackComponent={ErrorFallback}>
294
+ <Suspense fallback={<div>Loading...</div>}>
295
+ <TestComponent />
296
+ </Suspense>
297
+ </ErrorBoundary>,
298
+ { account },
299
+ );
300
+ });
301
+
302
+ await waitFor(
303
+ () => {
304
+ expect(container.textContent).toContain("Error: deleted");
305
+ },
306
+ { timeout: 10_000 },
307
+ );
308
+ });
309
+
242
310
  it("should throw error for anonymous agent", async () => {
243
311
  const MyAppAccount = co.account({
244
312
  profile: co.profile({
@@ -218,6 +218,50 @@ describe("useSuspenseCoState", () => {
218
218
  );
219
219
  });
220
220
 
221
+ it("should throw error when CoValue is deleted", async () => {
222
+ const TestMap = co.map({
223
+ value: z.string(),
224
+ });
225
+
226
+ const owner = await createJazzTestAccount({
227
+ isCurrentActiveAccount: true,
228
+ });
229
+
230
+ const map = TestMap.create(
231
+ {
232
+ value: "123",
233
+ },
234
+ Group.create(owner).makePublic("reader"),
235
+ );
236
+
237
+ map.$jazz.raw.core.deleteCoValue();
238
+
239
+ const TestComponent = () => {
240
+ const value = useSuspenseCoState(TestMap, map.$jazz.id);
241
+ return <div>{value.value}</div>;
242
+ };
243
+
244
+ const { container } = await act(async () => {
245
+ return render(
246
+ <ErrorBoundary FallbackComponent={ErrorFallback}>
247
+ <Suspense fallback={<div>Loading...</div>}>
248
+ <TestComponent />
249
+ </Suspense>
250
+ </ErrorBoundary>,
251
+ {
252
+ account: owner,
253
+ },
254
+ );
255
+ });
256
+
257
+ await waitFor(
258
+ () => {
259
+ expect(container.textContent).toContain("Error: deleted");
260
+ },
261
+ { timeout: 10_000 },
262
+ );
263
+ });
264
+
221
265
  it("should throw error when CoValue is unavailable due disabled network", async () => {
222
266
  disableJazzTestSync();
223
267
 
@@ -33,7 +33,6 @@ export type JazzContextManagerProps<
33
33
  onAnonymousAccountDiscarded?: (
34
34
  anonymousAccount: InstanceOfSchema<S>,
35
35
  ) => Promise<void>;
36
- CryptoProvider?: BaseReactNativeContextOptions["CryptoProvider"];
37
36
  };
38
37
 
39
38
  export class ReactNativeContextManager<
@@ -50,7 +49,6 @@ export class ReactNativeContextManager<
50
49
  sync: props.sync,
51
50
  storage: props.storage,
52
51
  authSecretStorage: this.authSecretStorage,
53
- CryptoProvider: props.CryptoProvider,
54
52
  });
55
53
  } else {
56
54
  return createJazzReactNativeContext<S>({
@@ -61,7 +59,6 @@ export class ReactNativeContextManager<
61
59
  newAccountProps: authProps?.newAccountProps,
62
60
  defaultProfileName: props.defaultProfileName,
63
61
  authSecretStorage: this.authSecretStorage,
64
- CryptoProvider: props.CryptoProvider,
65
62
  });
66
63
  }
67
64
  }
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  useAuthSecretStorage,
3
3
  useIsAuthenticated,
4
- useJazzContext,
4
+ useJazzContextValue,
5
5
  } from "jazz-tools/react-core";
6
6
  import { useMemo } from "react";
7
7
  import { ReactNativePasskeyAuth } from "./PasskeyAuth.js";
@@ -52,7 +52,7 @@ export function usePasskeyAuth({
52
52
  appName: string;
53
53
  rpId: string;
54
54
  }) {
55
- const context = useJazzContext();
55
+ const context = useJazzContextValue();
56
56
  const authSecretStorage = useAuthSecretStorage();
57
57
 
58
58
  if ("guest" in context) {
@@ -1,7 +1,7 @@
1
1
  import { useEffect } from "react";
2
2
 
3
3
  import { CoValueClassOrSchema, parseInviteLink } from "jazz-tools";
4
- import { useJazzContext } from "jazz-tools/react-core";
4
+ import { useJazzContextValue } from "jazz-tools/react-core";
5
5
  import { Linking } from "react-native";
6
6
 
7
7
  export {
@@ -10,7 +10,7 @@ export {
10
10
  experimental_useInboxSender,
11
11
  useDemoAuth,
12
12
  usePassphraseAuth,
13
- useJazzContext,
13
+ useJazzContextValue,
14
14
  useAuthSecretStorage,
15
15
  useIsAuthenticated,
16
16
  useAccount,
@@ -34,7 +34,7 @@ export function useAcceptInviteNative<S extends CoValueClassOrSchema>({
34
34
  onAccept: (projectID: string) => void;
35
35
  forValueHint?: string;
36
36
  }): void {
37
- const context = useJazzContext();
37
+ const context = useJazzContextValue();
38
38
 
39
39
  if (!("me" in context)) {
40
40
  throw new Error(
@@ -1,6 +1,5 @@
1
1
  import NetInfo from "@react-native-community/netinfo";
2
2
  import { LocalNode, Peer, getSqliteStorageAsync } from "cojson";
3
- import { PureJSCrypto } from "cojson/dist/crypto/PureJSCrypto"; // Importing from dist to not rely on the exports field
4
3
  import {
5
4
  Account,
6
5
  AccountClass,
@@ -20,14 +19,12 @@ import { ReactNativeSessionProvider } from "./ReactNativeSessionProvider.js";
20
19
 
21
20
  import { SQLiteDatabaseDriverAsync } from "cojson";
22
21
  import { WebSocketPeerWithReconnection } from "cojson-transport-ws";
23
- import type { RNQuickCrypto } from "jazz-tools/react-native-core/crypto";
24
- import type { RNCrypto } from "cojson/crypto/RNCrypto";
22
+ import { RNCrypto } from "cojson/crypto/RNCrypto";
25
23
 
26
24
  export type BaseReactNativeContextOptions = {
27
25
  sync: SyncConfig;
28
26
  reconnectionTimeout?: number;
29
27
  storage?: SQLiteDatabaseDriverAsync | "disabled";
30
- CryptoProvider?: typeof PureJSCrypto | typeof RNQuickCrypto | typeof RNCrypto;
31
28
  authSecretStorage: AuthSecretStorage;
32
29
  };
33
30
 
@@ -40,8 +37,7 @@ class ReactNativeWebSocketPeerWithReconnection extends WebSocketPeerWithReconnec
40
37
  }
41
38
 
42
39
  async function setupPeers(options: BaseReactNativeContextOptions) {
43
- const CryptoProvider = options.CryptoProvider || PureJSCrypto;
44
- const crypto = await CryptoProvider.create();
40
+ const crypto = await RNCrypto.create();
45
41
  let node: LocalNode | undefined = undefined;
46
42
 
47
43
  const peers: Peer[] = [];
@@ -3,12 +3,17 @@ import {
3
3
  AccountClass,
4
4
  AnyAccountSchema,
5
5
  CoValueFromRaw,
6
- InstanceOfSchema,
7
- JazzContextType,
8
6
  KvStore,
9
7
  } from "jazz-tools";
10
- import { JazzContext, JazzContextManagerContext } from "jazz-tools/react-core";
11
- import React, { useEffect, useRef } from "react";
8
+ import { JazzContext } from "jazz-tools/react-core";
9
+ import React, {
10
+ useCallback,
11
+ useContext,
12
+ useEffect,
13
+ useMemo,
14
+ useRef,
15
+ useSyncExternalStore,
16
+ } from "react";
12
17
  import type { JazzContextManagerProps } from "./ReactNativeContextManager.js";
13
18
  import { ReactNativeContextManager } from "./ReactNativeContextManager.js";
14
19
  import { setupKvStore } from "./platform.js";
@@ -20,6 +25,7 @@ export type JazzProviderProps<
20
25
  > = {
21
26
  children: React.ReactNode;
22
27
  kvStore?: KvStore;
28
+ fallback?: React.ReactNode | null;
23
29
  authSecretStorageKey?: string;
24
30
  } & JazzContextManagerProps<S>;
25
31
 
@@ -39,9 +45,15 @@ export function JazzProviderCore<
39
45
  logOutReplacement,
40
46
  onAnonymousAccountDiscarded,
41
47
  kvStore,
42
- CryptoProvider,
43
48
  authSecretStorageKey,
49
+ fallback = null,
44
50
  }: JazzProviderProps<S>) {
51
+ if (useContext(JazzContext)) {
52
+ throw new Error(
53
+ "You can't nest a JazzProvider inside another JazzProvider.",
54
+ );
55
+ }
56
+
45
57
  setupKvStore(kvStore);
46
58
 
47
59
  const [contextManager] = React.useState(
@@ -53,46 +65,40 @@ export function JazzProviderCore<
53
65
  const onAnonymousAccountDiscardedRefCallback = useRefCallback(
54
66
  onAnonymousAccountDiscarded,
55
67
  );
56
- const logoutReplacementActiveRef = useRef(false);
57
- logoutReplacementActiveRef.current = Boolean(logOutReplacement);
58
- const onAnonymousAccountDiscardedEnabled = Boolean(
59
- onAnonymousAccountDiscarded,
60
- );
61
68
 
62
- const value = React.useSyncExternalStore<
63
- JazzContextType<InstanceOfSchema<S>> | undefined
64
- >(
65
- React.useCallback(
66
- (callback) => {
67
- const props = {
68
- AccountSchema,
69
- guestMode,
70
- sync,
71
- storage,
72
- defaultProfileName,
73
- onLogOut: onLogOutRefCallback,
74
- logOutReplacement: logoutReplacementActiveRef.current
75
- ? logOutReplacementRefCallback
76
- : undefined,
77
- onAnonymousAccountDiscarded: onAnonymousAccountDiscardedEnabled
78
- ? onAnonymousAccountDiscardedRefCallback
79
- : undefined,
80
- CryptoProvider,
81
- } satisfies JazzContextManagerProps<S>;
69
+ const props = useMemo(() => {
70
+ return {
71
+ AccountSchema,
72
+ guestMode,
73
+ sync,
74
+ storage,
75
+ defaultProfileName,
76
+ onLogOut: onLogOutRefCallback,
77
+ logOutReplacement: logOutReplacement
78
+ ? logOutReplacementRefCallback
79
+ : undefined,
80
+ onAnonymousAccountDiscarded: onAnonymousAccountDiscarded
81
+ ? onAnonymousAccountDiscardedRefCallback
82
+ : undefined,
83
+ } satisfies JazzContextManagerProps<S>;
84
+ }, [guestMode, sync.peer, sync.when, storage]);
82
85
 
83
- if (contextManager.propsChanged(props)) {
84
- contextManager.createContext(props).catch((error) => {
85
- console.log(error.stack);
86
- console.error("Error creating Jazz React Native context:", error);
87
- });
88
- }
86
+ if (contextManager.propsChanged(props)) {
87
+ contextManager.createContext(props).catch((error) => {
88
+ console.log(error.stack);
89
+ console.error("Error creating Jazz React Native context:", error);
90
+ });
91
+ }
89
92
 
93
+ const isReady = useSyncExternalStore(
94
+ useCallback(
95
+ (callback) => {
90
96
  return contextManager.subscribe(callback);
91
97
  },
92
- [sync, guestMode].concat(storage as any),
98
+ [contextManager],
93
99
  ),
94
- () => contextManager.getCurrentValue(),
95
- () => contextManager.getCurrentValue(),
100
+ () => Boolean(contextManager.getCurrentValue()),
101
+ () => Boolean(contextManager.getCurrentValue()),
96
102
  );
97
103
 
98
104
  useEffect(() => {
@@ -106,10 +112,8 @@ export function JazzProviderCore<
106
112
  }, []);
107
113
 
108
114
  return (
109
- <JazzContext.Provider value={value}>
110
- <JazzContextManagerContext.Provider value={contextManager}>
111
- {value && children}
112
- </JazzContextManagerContext.Provider>
115
+ <JazzContext.Provider value={contextManager}>
116
+ {isReady ? children : fallback}
113
117
  </JazzContext.Provider>
114
118
  );
115
119
  }
@@ -99,10 +99,7 @@ export class CoState<
99
99
  // @ts-expect-error The resolve query type isn't compatible with the coValueClassFromCoValueClassOrSchema conversion
100
100
  resolve,
101
101
  loadAs: agent,
102
- onUnavailable: (value) => {
103
- this.update(value);
104
- },
105
- onUnauthorized: (value) => {
102
+ onError: (value) => {
106
103
  this.update(value);
107
104
  },
108
105
  syncResolution: true,
@@ -181,10 +178,7 @@ export class AccountCoState<
181
178
  {
182
179
  resolve,
183
180
  loadAs: me,
184
- onUnavailable: (value) => {
185
- this.update(value);
186
- },
187
- onUnauthorized: (value) => {
181
+ onError: (value) => {
188
182
  this.update(value);
189
183
  },
190
184
  syncResolution: true,
@@ -0,0 +1,79 @@
1
+ // @vitest-environment happy-dom
2
+
3
+ import { CoValueLoadingState, co, z } from "jazz-tools";
4
+ import { assertLoaded } from "jazz-tools/testing";
5
+ import { beforeEach, describe, expect, it } from "vitest";
6
+ import { createJazzTestAccount, setupJazzTestSync } from "../testing";
7
+ import { render, screen, waitFor } from "./testUtils";
8
+ import TestAccountCoStateWrapper from "./TestAccountCoStateWrapper.svelte";
9
+
10
+ describe("AccountCoState", () => {
11
+ beforeEach(async () => {
12
+ await setupJazzTestSync();
13
+ });
14
+
15
+ it("should return a 'deleted' value when a required resolved child is deleted", async () => {
16
+ const AccountRoot = co.map({
17
+ value: z.string(),
18
+ });
19
+
20
+ const AccountSchema = co
21
+ .account({
22
+ profile: co.profile(),
23
+ root: AccountRoot,
24
+ })
25
+ .withMigration((account) => {
26
+ if (!account.$jazz.refs.root) {
27
+ account.$jazz.set("root", { value: "123" });
28
+ }
29
+ });
30
+
31
+ const account = await createJazzTestAccount({
32
+ AccountSchema,
33
+ isCurrentActiveAccount: true,
34
+ });
35
+
36
+ render(
37
+ TestAccountCoStateWrapper,
38
+ {
39
+ Schema: AccountSchema,
40
+ options: {
41
+ resolve: {
42
+ root: true,
43
+ },
44
+ },
45
+ },
46
+ {
47
+ account,
48
+ },
49
+ );
50
+
51
+ // Ensure the account (and root) is loaded first.
52
+ const loaded = await account.$jazz.ensureLoaded({
53
+ resolve: {
54
+ root: true,
55
+ },
56
+ });
57
+ assertLoaded(loaded.root);
58
+
59
+ await waitFor(() => {
60
+ expect(screen.getByTestId("loading-state").textContent).toBe(
61
+ CoValueLoadingState.LOADED,
62
+ );
63
+ expect(screen.getByTestId("is-loaded").textContent).toBe("true");
64
+ });
65
+
66
+ // Delete the required child (root) -> AccountCoState should bubble the error.
67
+ loaded.root.$jazz.raw.core.deleteCoValue();
68
+ await loaded.root.$jazz.raw.core.waitForSync();
69
+
70
+ await waitFor(() => {
71
+ expect(screen.getByTestId("loading-state").textContent).toBe(
72
+ CoValueLoadingState.DELETED,
73
+ );
74
+ expect(screen.getByTestId("is-loaded").textContent).toBe("false");
75
+ });
76
+ });
77
+ });
78
+
79
+
@@ -54,4 +54,40 @@ describe("CoState", () => {
54
54
  );
55
55
  expect(stateValue.name).toBe("John Doe");
56
56
  });
57
+
58
+ it("should return a 'deleted' value when the coValue is deleted", async () => {
59
+ const Person = co.map({
60
+ name: co.plainText(),
61
+ });
62
+ const PersonWithName = Person.resolved({ name: true });
63
+ const person = Person.create({ name: "John Doe" }, publicGroup);
64
+
65
+ render(
66
+ TestCoStateWrapper,
67
+ {
68
+ Schema: PersonWithName,
69
+ id: person.$jazz.id,
70
+ },
71
+ {
72
+ account: clientAccount,
73
+ },
74
+ );
75
+
76
+ await waitFor(() => {
77
+ expect(screen.getByTestId("loading-state").textContent).toBe(
78
+ CoValueLoadingState.LOADED,
79
+ );
80
+ });
81
+
82
+ // Delete on the server (owner/admin) and ensure it propagates.
83
+ person.$jazz.raw.core.deleteCoValue();
84
+ await person.$jazz.raw.core.waitForSync();
85
+
86
+ await waitFor(() => {
87
+ expect(screen.getByTestId("loading-state").textContent).toBe(
88
+ CoValueLoadingState.DELETED,
89
+ );
90
+ expect(screen.getByTestId("is-loaded").textContent).toBe("false");
91
+ });
92
+ });
57
93
  });