jazz-tools 0.19.20 → 0.19.22
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/.svelte-kit/__package__/server.d.ts.map +1 -1
- package/.svelte-kit/__package__/server.js +9 -7
- package/.turbo/turbo-build.log +56 -56
- package/CHANGELOG.md +15 -0
- package/dist/better-auth/auth/server.d.ts.map +1 -1
- package/dist/better-auth/auth/server.js +4 -4
- package/dist/better-auth/auth/server.js.map +1 -1
- package/dist/better-auth/database-adapter/index.js.map +1 -1
- package/dist/better-auth/database-adapter/repository/generic.d.ts +3 -3
- package/dist/better-auth/database-adapter/repository/session.d.ts +2 -2
- package/dist/better-auth/database-adapter/schema.d.ts +3 -3
- package/dist/better-auth/database-adapter/schema.d.ts.map +1 -1
- package/dist/{chunk-MI24YFCY.js → chunk-QCTQH5RS.js} +1 -1
- package/dist/chunk-QCTQH5RS.js.map +1 -0
- package/dist/index.js +36 -1
- package/dist/index.js.map +1 -1
- package/dist/media/{chunk-3LKBM3G3.js → chunk-IRL3KNPO.js} +2 -2
- package/dist/media/{chunk-3LKBM3G3.js.map → chunk-IRL3KNPO.js.map} +1 -1
- package/dist/media/create-image/react-native.d.ts +1 -1
- package/dist/media/create-image/react-native.d.ts.map +1 -1
- package/dist/media/index.browser.js +1 -1
- package/dist/media/index.js +1 -1
- package/dist/media/index.native.js +5 -5
- package/dist/media/index.native.js.map +1 -1
- package/dist/media/index.server.js +1 -1
- package/dist/react/hooks.d.ts +1 -2
- package/dist/react/hooks.d.ts.map +1 -1
- package/dist/react/index.js +7 -2
- package/dist/react/index.js.map +1 -1
- package/dist/react-core/hooks.d.ts +92 -1
- package/dist/react-core/hooks.d.ts.map +1 -1
- package/dist/react-core/index.js +126 -57
- package/dist/react-core/index.js.map +1 -1
- package/dist/react-core/tests/useCoStates.test.d.ts +2 -0
- package/dist/react-core/tests/useCoStates.test.d.ts.map +1 -0
- package/dist/react-native/index.js +4 -0
- package/dist/react-native/index.js.map +1 -1
- 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 +4 -0
- package/dist/react-native-core/index.js.map +1 -1
- package/dist/svelte/auth/ClerkAuth.svelte.d.ts +38 -0
- package/dist/svelte/auth/ClerkAuth.svelte.d.ts.map +1 -0
- package/dist/svelte/auth/ClerkAuth.svelte.js +47 -0
- package/dist/svelte/auth/JazzSvelteProviderWithClerk.svelte +156 -0
- package/dist/svelte/auth/JazzSvelteProviderWithClerk.svelte.d.ts +67 -0
- package/dist/svelte/auth/JazzSvelteProviderWithClerk.svelte.d.ts.map +1 -0
- package/dist/svelte/auth/RegisterClerkAuth.svelte +27 -0
- package/dist/svelte/auth/RegisterClerkAuth.svelte.d.ts +17 -0
- package/dist/svelte/auth/RegisterClerkAuth.svelte.d.ts.map +1 -0
- package/dist/svelte/auth/index.d.ts +2 -0
- package/dist/svelte/auth/index.d.ts.map +1 -1
- package/dist/svelte/auth/index.js +2 -0
- package/dist/svelte/tests/ClerkAuth.svelte.test.d.ts +2 -0
- package/dist/svelte/tests/ClerkAuth.svelte.test.d.ts.map +1 -0
- package/dist/svelte/tests/ClerkAuth.svelte.test.js +202 -0
- package/dist/svelte/tests/TestClerkAuthWrapper.svelte +16 -0
- package/dist/svelte/tests/TestClerkAuthWrapper.svelte.d.ts +8 -0
- package/dist/svelte/tests/TestClerkAuthWrapper.svelte.d.ts.map +1 -0
- package/dist/svelte/tests/testUtils.d.ts +1 -0
- package/dist/svelte/tests/testUtils.d.ts.map +1 -1
- package/dist/svelte/tests/testUtils.js +3 -1
- package/dist/testing.js +1 -1
- package/dist/tools/auth/clerk/index.d.ts +1 -1
- package/dist/tools/auth/clerk/types.d.ts +1 -1
- package/dist/tools/auth/clerk/types.d.ts.map +1 -1
- package/dist/tools/exports.d.ts +1 -0
- package/dist/tools/exports.d.ts.map +1 -1
- package/dist/tools/subscribe/types.d.ts +1 -1
- package/dist/tools/subscribe/types.d.ts.map +1 -1
- package/dist/worker/JazzMessageChannel.d.ts +36 -0
- package/dist/worker/JazzMessageChannel.d.ts.map +1 -0
- package/dist/worker/index.d.ts +7 -1
- package/dist/worker/index.d.ts.map +1 -1
- package/dist/worker/index.js +28 -17
- package/dist/worker/index.js.map +1 -1
- package/package.json +4 -4
- package/src/better-auth/auth/server.ts +9 -7
- package/src/better-auth/database-adapter/repository/generic.ts +3 -3
- package/src/better-auth/database-adapter/repository/session.ts +2 -2
- package/src/better-auth/database-adapter/schema.ts +5 -5
- package/src/media/create-image/react-native.ts +9 -7
- package/src/media/create-image-factory.test.ts +1 -1
- package/src/media/create-image-factory.ts +1 -1
- package/src/react/hooks.tsx +4 -2
- package/src/react-core/hooks.ts +321 -76
- package/src/react-core/tests/testUtils.tsx +2 -2
- package/src/react-core/tests/useCoState.selector.test.ts +309 -22
- package/src/react-core/tests/useCoStates.test.tsx +414 -0
- package/src/react-native-core/hooks.tsx +2 -0
- package/src/svelte/auth/ClerkAuth.svelte.ts +67 -0
- package/src/svelte/auth/JazzSvelteProviderWithClerk.svelte +156 -0
- package/src/svelte/auth/RegisterClerkAuth.svelte +27 -0
- package/src/svelte/auth/index.ts +2 -0
- package/src/svelte/tests/ClerkAuth.svelte.test.ts +305 -0
- package/src/svelte/tests/TestClerkAuthWrapper.svelte +16 -0
- package/src/svelte/tests/testUtils.ts +4 -1
- package/src/tools/auth/clerk/types.ts +1 -1
- package/src/tools/exports.ts +5 -0
- package/src/tools/subscribe/types.ts +1 -1
- package/src/tools/tests/inbox.test.ts +7 -7
- package/src/tools/tests/testStorage.ts +2 -2
- package/src/worker/JazzMessageChannel.ts +73 -0
- package/src/worker/index.ts +36 -17
- package/dist/chunk-MI24YFCY.js.map +0 -1
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Account,
|
|
5
|
+
InMemoryKVStore,
|
|
6
|
+
JazzClerkAuth,
|
|
7
|
+
KvStoreContext,
|
|
8
|
+
} from "jazz-tools";
|
|
9
|
+
import type { MinimalClerkClient } from "jazz-tools";
|
|
10
|
+
import { render as renderSvelte, waitFor } from "@testing-library/svelte";
|
|
11
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
12
|
+
import {
|
|
13
|
+
createJazzTestAccount,
|
|
14
|
+
createJazzTestGuest,
|
|
15
|
+
setupJazzTestSync,
|
|
16
|
+
} from "../testing";
|
|
17
|
+
import { render, screen } from "./testUtils";
|
|
18
|
+
import TestClerkAuthWrapper from "./TestClerkAuthWrapper.svelte";
|
|
19
|
+
import JazzSvelteProviderWithClerk from "../auth/JazzSvelteProviderWithClerk.svelte";
|
|
20
|
+
|
|
21
|
+
KvStoreContext.getInstance().initialize(new InMemoryKVStore());
|
|
22
|
+
|
|
23
|
+
function createMockClerkClient(
|
|
24
|
+
user: MinimalClerkClient["user"] = null,
|
|
25
|
+
): MinimalClerkClient {
|
|
26
|
+
return {
|
|
27
|
+
user,
|
|
28
|
+
signOut: vi.fn(),
|
|
29
|
+
addListener: vi.fn(() => () => {}),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe("useClerkAuth", () => {
|
|
34
|
+
let account: Account;
|
|
35
|
+
|
|
36
|
+
beforeEach(async () => {
|
|
37
|
+
await setupJazzTestSync();
|
|
38
|
+
account = await createJazzTestAccount({
|
|
39
|
+
isCurrentActiveAccount: true,
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("should return anonymous state when not authenticated", async () => {
|
|
44
|
+
const mockClerk = createMockClerkClient();
|
|
45
|
+
|
|
46
|
+
render(
|
|
47
|
+
TestClerkAuthWrapper,
|
|
48
|
+
{ clerk: mockClerk },
|
|
49
|
+
{ account, isAuthenticated: false },
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
expect(screen.getByTestId("auth-state").textContent).toBe("anonymous");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should return signedIn state when authenticated", async () => {
|
|
56
|
+
const mockClerk = createMockClerkClient({
|
|
57
|
+
id: "user_123",
|
|
58
|
+
fullName: "Test User",
|
|
59
|
+
username: "testuser",
|
|
60
|
+
firstName: "Test",
|
|
61
|
+
lastName: "User",
|
|
62
|
+
primaryEmailAddress: { emailAddress: "test@example.com" },
|
|
63
|
+
unsafeMetadata: {
|
|
64
|
+
jazzAccountID: "test123",
|
|
65
|
+
jazzAccountSecret: "secret123",
|
|
66
|
+
},
|
|
67
|
+
update: vi.fn(),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
render(
|
|
71
|
+
TestClerkAuthWrapper,
|
|
72
|
+
{ clerk: mockClerk },
|
|
73
|
+
{ account, isAuthenticated: true },
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
expect(screen.getByTestId("auth-state").textContent).toBe("signedIn");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("should register the clerk listener", async () => {
|
|
80
|
+
const mockClerk = createMockClerkClient();
|
|
81
|
+
|
|
82
|
+
render(
|
|
83
|
+
TestClerkAuthWrapper,
|
|
84
|
+
{ clerk: mockClerk },
|
|
85
|
+
{ account, isAuthenticated: false },
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
expect(mockClerk.addListener).toHaveBeenCalled();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("should cleanup listener on unmount", async () => {
|
|
92
|
+
const mockUnsubscribe = vi.fn();
|
|
93
|
+
const mockClerk = createMockClerkClient();
|
|
94
|
+
mockClerk.addListener = vi.fn(() => mockUnsubscribe);
|
|
95
|
+
|
|
96
|
+
const { unmount } = render(
|
|
97
|
+
TestClerkAuthWrapper,
|
|
98
|
+
{ clerk: mockClerk },
|
|
99
|
+
{ account, isAuthenticated: false },
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
expect(mockClerk.addListener).toHaveBeenCalled();
|
|
103
|
+
unmount();
|
|
104
|
+
expect(mockUnsubscribe).toHaveBeenCalled();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("should throw error in guest mode", async () => {
|
|
108
|
+
const guest = await createJazzTestGuest();
|
|
109
|
+
const mockClerk = createMockClerkClient();
|
|
110
|
+
|
|
111
|
+
expect(() => {
|
|
112
|
+
render(TestClerkAuthWrapper, { clerk: mockClerk }, { account: guest });
|
|
113
|
+
}).toThrow("Clerk auth is not supported in guest mode");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("should call listener with clerk client events", async () => {
|
|
117
|
+
const mockClerk = createMockClerkClient();
|
|
118
|
+
let listenerCallback: ((data: unknown) => void) | undefined;
|
|
119
|
+
mockClerk.addListener = vi.fn((callback) => {
|
|
120
|
+
listenerCallback = callback;
|
|
121
|
+
return () => {};
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
render(
|
|
125
|
+
TestClerkAuthWrapper,
|
|
126
|
+
{ clerk: mockClerk },
|
|
127
|
+
{ account, isAuthenticated: false },
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
expect(mockClerk.addListener).toHaveBeenCalled();
|
|
131
|
+
expect(listenerCallback).toBeDefined();
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe("JazzClerkAuth.initializeAuth", () => {
|
|
136
|
+
afterEach(() => {
|
|
137
|
+
vi.restoreAllMocks();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("should handle initialization errors gracefully", async () => {
|
|
141
|
+
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
142
|
+
const initializeAuthSpy = vi
|
|
143
|
+
.spyOn(JazzClerkAuth, "initializeAuth")
|
|
144
|
+
.mockRejectedValue(new Error("Test error"));
|
|
145
|
+
|
|
146
|
+
const mockClerk = createMockClerkClient();
|
|
147
|
+
|
|
148
|
+
// The error should be thrown by the mock
|
|
149
|
+
await expect(JazzClerkAuth.initializeAuth(mockClerk)).rejects.toThrow(
|
|
150
|
+
"Test error",
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
// Restore using vitest's cleanup
|
|
154
|
+
initializeAuthSpy.mockRestore();
|
|
155
|
+
consoleSpy.mockRestore();
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe("JazzSvelteProviderWithClerk", () => {
|
|
160
|
+
afterEach(() => {
|
|
161
|
+
vi.restoreAllMocks();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("should render children after successful initialization", async () => {
|
|
165
|
+
const mockClerk = createMockClerkClient();
|
|
166
|
+
vi.spyOn(JazzClerkAuth, "initializeAuth").mockResolvedValue(undefined);
|
|
167
|
+
|
|
168
|
+
const { container } = renderSvelte(JazzSvelteProviderWithClerk, {
|
|
169
|
+
props: {
|
|
170
|
+
clerk: mockClerk,
|
|
171
|
+
sync: { peer: "wss://test.example.com" },
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
await waitFor(() => {
|
|
176
|
+
// After initialization, the provider should render (even without children)
|
|
177
|
+
// The error div should not be present
|
|
178
|
+
expect(
|
|
179
|
+
container.querySelector('[data-testid="jazz-clerk-auth-error"]'),
|
|
180
|
+
).toBeNull();
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("should show default error message when initialization fails", async () => {
|
|
185
|
+
const mockClerk = createMockClerkClient();
|
|
186
|
+
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
187
|
+
vi.spyOn(JazzClerkAuth, "initializeAuth").mockRejectedValue(
|
|
188
|
+
new Error("Init failed"),
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
const { container } = renderSvelte(JazzSvelteProviderWithClerk, {
|
|
192
|
+
props: {
|
|
193
|
+
clerk: mockClerk,
|
|
194
|
+
sync: { peer: "wss://test.example.com" },
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
await waitFor(() => {
|
|
199
|
+
const errorDiv = container.querySelector(
|
|
200
|
+
'[data-testid="jazz-clerk-auth-error"]',
|
|
201
|
+
);
|
|
202
|
+
expect(errorDiv).not.toBeNull();
|
|
203
|
+
expect(errorDiv?.textContent).toContain(
|
|
204
|
+
"Authentication initialization failed",
|
|
205
|
+
);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
consoleSpy.mockRestore();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("should call onAuthError callback when initialization fails", async () => {
|
|
212
|
+
const mockClerk = createMockClerkClient();
|
|
213
|
+
const onAuthError = vi.fn();
|
|
214
|
+
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
215
|
+
vi.spyOn(JazzClerkAuth, "initializeAuth").mockRejectedValue(
|
|
216
|
+
new Error("Init failed"),
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
renderSvelte(JazzSvelteProviderWithClerk, {
|
|
220
|
+
props: {
|
|
221
|
+
clerk: mockClerk,
|
|
222
|
+
sync: { peer: "wss://test.example.com" },
|
|
223
|
+
onAuthError,
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
await waitFor(() => {
|
|
228
|
+
expect(onAuthError).toHaveBeenCalledWith(expect.any(Error));
|
|
229
|
+
expect(onAuthError).toHaveBeenCalledWith(
|
|
230
|
+
expect.objectContaining({ message: "Init failed" }),
|
|
231
|
+
);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
consoleSpy.mockRestore();
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("should not update state after unmount (cancellation)", async () => {
|
|
238
|
+
const mockClerk = createMockClerkClient();
|
|
239
|
+
let resolveInit: () => void;
|
|
240
|
+
const initPromise = new Promise<void>((resolve) => {
|
|
241
|
+
resolveInit = resolve;
|
|
242
|
+
});
|
|
243
|
+
vi.spyOn(JazzClerkAuth, "initializeAuth").mockReturnValue(initPromise);
|
|
244
|
+
|
|
245
|
+
const { unmount } = renderSvelte(JazzSvelteProviderWithClerk, {
|
|
246
|
+
props: {
|
|
247
|
+
clerk: mockClerk,
|
|
248
|
+
sync: { peer: "wss://test.example.com" },
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Unmount before initialization completes
|
|
253
|
+
unmount();
|
|
254
|
+
|
|
255
|
+
// Resolve the promise after unmount - should not cause errors
|
|
256
|
+
resolveInit!();
|
|
257
|
+
|
|
258
|
+
// Give time for any potential state updates
|
|
259
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
260
|
+
|
|
261
|
+
// If we get here without errors, the cancellation worked
|
|
262
|
+
expect(true).toBe(true);
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
describe("useClerkAuth reactive state transitions", () => {
|
|
267
|
+
let account: Account;
|
|
268
|
+
|
|
269
|
+
beforeEach(async () => {
|
|
270
|
+
await setupJazzTestSync();
|
|
271
|
+
account = await createJazzTestAccount({
|
|
272
|
+
isCurrentActiveAccount: true,
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
afterEach(() => {
|
|
277
|
+
vi.restoreAllMocks();
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("should update state reactively when listener callback fires", async () => {
|
|
281
|
+
const mockClerk = createMockClerkClient();
|
|
282
|
+
let listenerCallback: ((data: unknown) => void) | undefined;
|
|
283
|
+
mockClerk.addListener = vi.fn((callback) => {
|
|
284
|
+
listenerCallback = callback;
|
|
285
|
+
return () => {};
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
const { container } = render(
|
|
289
|
+
TestClerkAuthWrapper,
|
|
290
|
+
{ clerk: mockClerk },
|
|
291
|
+
{ account, isAuthenticated: false },
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
// Initial state should be anonymous
|
|
295
|
+
expect(screen.getByTestId("auth-state").textContent).toBe("anonymous");
|
|
296
|
+
|
|
297
|
+
// Verify listener was registered
|
|
298
|
+
expect(listenerCallback).toBeDefined();
|
|
299
|
+
|
|
300
|
+
// Note: Full state transition testing would require more complex setup
|
|
301
|
+
// involving the actual JazzClerkAuth.onClerkUserChange flow.
|
|
302
|
+
// This test verifies the listener registration which is the foundation
|
|
303
|
+
// for reactive updates.
|
|
304
|
+
});
|
|
305
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { MinimalClerkClient } from "jazz-tools";
|
|
3
|
+
import { useClerkAuth } from "../auth/ClerkAuth.svelte.js";
|
|
4
|
+
|
|
5
|
+
type Props = {
|
|
6
|
+
clerk: MinimalClerkClient;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
let { clerk }: Props = $props();
|
|
10
|
+
|
|
11
|
+
const auth = useClerkAuth(clerk);
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<div data-testid="clerk-auth-wrapper">
|
|
15
|
+
<div data-testid="auth-state">{auth.state}</div>
|
|
16
|
+
</div>
|
|
@@ -6,6 +6,7 @@ import { JAZZ_AUTH_CTX, JAZZ_CTX } from "../jazz.svelte";
|
|
|
6
6
|
|
|
7
7
|
type JazzExtendedOptions = {
|
|
8
8
|
account: Account | { guest: AnonymousJazzAgent };
|
|
9
|
+
isAuthenticated?: boolean;
|
|
9
10
|
};
|
|
10
11
|
|
|
11
12
|
const render = <T extends Component>(
|
|
@@ -13,7 +14,9 @@ const render = <T extends Component>(
|
|
|
13
14
|
props: ComponentProps<T>,
|
|
14
15
|
jazzOptions: JazzExtendedOptions,
|
|
15
16
|
) => {
|
|
16
|
-
const ctx = TestJazzContextManager.fromAccountOrGuest(jazzOptions.account
|
|
17
|
+
const ctx = TestJazzContextManager.fromAccountOrGuest(jazzOptions.account, {
|
|
18
|
+
isAuthenticated: jazzOptions.isAuthenticated,
|
|
19
|
+
});
|
|
17
20
|
|
|
18
21
|
return renderSvelte(
|
|
19
22
|
// @ts-expect-error Svelte new Component type is not compatible with @testing-library/svelte
|
|
@@ -47,7 +47,7 @@ type PermissiveClerkUser = Omit<ClerkUser, "unsafeMetadata" | "update"> & {
|
|
|
47
47
|
export type MinimalClerkClient = {
|
|
48
48
|
user: PermissiveClerkUser | null | undefined;
|
|
49
49
|
signOut: () => Promise<void>;
|
|
50
|
-
addListener: (listener: (data: unknown) => void) => void;
|
|
50
|
+
addListener: (listener: (data: unknown) => void) => (() => void) | void;
|
|
51
51
|
};
|
|
52
52
|
|
|
53
53
|
export type ClerkCredentials = {
|
package/src/tools/exports.ts
CHANGED
|
@@ -144,3 +144,8 @@ export * from "./ssr/index.js";
|
|
|
144
144
|
export { captureStack } from "./subscribe/errorReporting.js";
|
|
145
145
|
|
|
146
146
|
export * as jazzConfig from "./config.js";
|
|
147
|
+
|
|
148
|
+
export {
|
|
149
|
+
JazzMessageChannel as experimental_JazzMessageChannel,
|
|
150
|
+
type JazzMessageChannelExposeOptions,
|
|
151
|
+
} from "../worker/JazzMessageChannel.js";
|
|
@@ -604,16 +604,16 @@ describe("Inbox", () => {
|
|
|
604
604
|
Message.create({ text: `Message ${i}`, value: i }, group),
|
|
605
605
|
);
|
|
606
606
|
|
|
607
|
-
// Subscribe with concurrency limit of
|
|
607
|
+
// Subscribe with concurrency limit of 3
|
|
608
608
|
const unsubscribe = receiverInbox.subscribe(
|
|
609
609
|
Message,
|
|
610
610
|
async (message) => {
|
|
611
611
|
const messageText = message.text;
|
|
612
612
|
processingOrder.push(`start-${messageText}`);
|
|
613
613
|
|
|
614
|
-
// Simulate processing time
|
|
614
|
+
// Simulate processing time (later messages take longer to ensure deterministic ordering)
|
|
615
615
|
await new Promise((resolve) =>
|
|
616
|
-
setTimeout(resolve,
|
|
616
|
+
setTimeout(resolve, 10 + message.value * 10),
|
|
617
617
|
);
|
|
618
618
|
|
|
619
619
|
processingOrder.push(`end-${messageText}`);
|
|
@@ -633,13 +633,13 @@ describe("Inbox", () => {
|
|
|
633
633
|
"start-Message 0",
|
|
634
634
|
"start-Message 1",
|
|
635
635
|
"start-Message 2",
|
|
636
|
-
"end-Message
|
|
636
|
+
"end-Message 0",
|
|
637
637
|
"start-Message 3",
|
|
638
|
-
"end-Message
|
|
638
|
+
"end-Message 1",
|
|
639
639
|
"start-Message 4",
|
|
640
|
+
"end-Message 2",
|
|
641
|
+
"end-Message 3",
|
|
640
642
|
"end-Message 4",
|
|
641
|
-
"end-Message 1",
|
|
642
|
-
"end-Message 0",
|
|
643
643
|
]
|
|
644
644
|
`);
|
|
645
645
|
unsubscribe();
|
|
@@ -50,8 +50,8 @@ export async function createAsyncStorage({ filename }: { filename?: string }) {
|
|
|
50
50
|
new LibSQLSqliteAsyncDriver(getDbPath(filename)),
|
|
51
51
|
);
|
|
52
52
|
|
|
53
|
-
onTestFinished(() => {
|
|
54
|
-
storage.close();
|
|
53
|
+
onTestFinished(async () => {
|
|
54
|
+
await storage.close();
|
|
55
55
|
});
|
|
56
56
|
|
|
57
57
|
return storage;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { CojsonMessageChannel, type Peer } from "cojson";
|
|
2
|
+
import type {
|
|
3
|
+
WaitForConnectionOptions,
|
|
4
|
+
ExposeOptions,
|
|
5
|
+
PostMessageTarget,
|
|
6
|
+
} from "cojson/src/CojsonMessageChannel/types.js";
|
|
7
|
+
import { Account, AnonymousJazzAgent } from "jazz-tools";
|
|
8
|
+
import { activeAccountContext } from "../tools/implementation/activeAccountContext.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Options for JazzMessageChannel.expose()
|
|
12
|
+
*/
|
|
13
|
+
export interface JazzMessageChannelExposeOptions extends ExposeOptions {
|
|
14
|
+
/**
|
|
15
|
+
* The account or anonymous agent to use for the connection.
|
|
16
|
+
* If not provided, falls back to the active account context.
|
|
17
|
+
*/
|
|
18
|
+
loadAs?: Account | AnonymousJazzAgent;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* JazzMessageChannel provides a high-level API for creating Jazz connections
|
|
23
|
+
* via the MessageChannel API. It wraps cojson's CojsonMessageChannel and
|
|
24
|
+
* automatically manages the node connection.
|
|
25
|
+
*/
|
|
26
|
+
export class JazzMessageChannel {
|
|
27
|
+
/**
|
|
28
|
+
* Expose a Jazz connection to a target.
|
|
29
|
+
* This is the host-side API, typically called from the main thread.
|
|
30
|
+
*
|
|
31
|
+
* @param target - Any object with a postMessage method (Worker, Window, etc.)
|
|
32
|
+
* @param opts - Configuration options including the account to use
|
|
33
|
+
* @returns A promise that resolves once the connection is established
|
|
34
|
+
*/
|
|
35
|
+
static async expose(
|
|
36
|
+
target: PostMessageTarget,
|
|
37
|
+
opts: JazzMessageChannelExposeOptions = {},
|
|
38
|
+
): Promise<void> {
|
|
39
|
+
const { loadAs, ...cojsonOpts } = opts;
|
|
40
|
+
|
|
41
|
+
// Get account from loadAs or fall back to active account context
|
|
42
|
+
const accountOrAgent = loadAs ?? activeAccountContext.maybeGet();
|
|
43
|
+
|
|
44
|
+
if (!accountOrAgent) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
"No account provided and no active account context available",
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const node =
|
|
51
|
+
accountOrAgent instanceof AnonymousJazzAgent
|
|
52
|
+
? accountOrAgent.node
|
|
53
|
+
: accountOrAgent.$jazz.localNode;
|
|
54
|
+
|
|
55
|
+
const peer = await CojsonMessageChannel.expose(target, cojsonOpts);
|
|
56
|
+
|
|
57
|
+
node.syncManager.addPeer(peer);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Accept an incoming Jazz connection.
|
|
62
|
+
* Same as cojson CojsonMessageChannel.waitForConnection().
|
|
63
|
+
*/
|
|
64
|
+
static waitForConnection(opts?: WaitForConnectionOptions): Promise<Peer> {
|
|
65
|
+
return CojsonMessageChannel.waitForConnection(opts);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Re-export types for convenience
|
|
70
|
+
export type {
|
|
71
|
+
WaitForConnectionOptions,
|
|
72
|
+
AcceptFromPortOptions,
|
|
73
|
+
} from "cojson/src/CojsonMessageChannel/types.js";
|
package/src/worker/index.ts
CHANGED
|
@@ -29,6 +29,11 @@ type WorkerOptions<
|
|
|
29
29
|
> = {
|
|
30
30
|
accountID?: string;
|
|
31
31
|
accountSecret?: string;
|
|
32
|
+
/**
|
|
33
|
+
* A peer to connect to for synchronization.
|
|
34
|
+
* If provided, syncServer is ignored.
|
|
35
|
+
*/
|
|
36
|
+
peer?: Peer;
|
|
32
37
|
syncServer?: string;
|
|
33
38
|
WebSocket?: AnyWebSocketConstructor;
|
|
34
39
|
AccountSchema?: S;
|
|
@@ -63,21 +68,28 @@ export async function startWorker<
|
|
|
63
68
|
|
|
64
69
|
const peers: Peer[] = [];
|
|
65
70
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
71
|
+
// If a peer is provided directly, use it instead of WebSocket
|
|
72
|
+
let wsPeer: WebSocketPeerWithReconnection | undefined;
|
|
73
|
+
|
|
74
|
+
if (options.peer) {
|
|
75
|
+
peers.push(options.peer);
|
|
76
|
+
} else {
|
|
77
|
+
wsPeer = new WebSocketPeerWithReconnection({
|
|
78
|
+
peer: syncServer,
|
|
79
|
+
reconnectionTimeout: 100,
|
|
80
|
+
addPeer: (peer) => {
|
|
81
|
+
if (node) {
|
|
82
|
+
node.syncManager.addPeer(peer);
|
|
83
|
+
} else {
|
|
84
|
+
peers.push(peer);
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
removePeer: () => {},
|
|
88
|
+
WebSocketConstructor: options.WebSocket,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
wsPeer.enable();
|
|
92
|
+
}
|
|
81
93
|
|
|
82
94
|
if (!accountID) {
|
|
83
95
|
throw new Error("No accountID provided");
|
|
@@ -117,7 +129,7 @@ export async function startWorker<
|
|
|
117
129
|
async function done() {
|
|
118
130
|
await context.account.$jazz.waitForAllCoValuesSync();
|
|
119
131
|
|
|
120
|
-
wsPeer
|
|
132
|
+
wsPeer?.disable();
|
|
121
133
|
context.done();
|
|
122
134
|
}
|
|
123
135
|
|
|
@@ -146,11 +158,18 @@ export async function startWorker<
|
|
|
146
158
|
* Wait for the connection to the sync server to be established.
|
|
147
159
|
*
|
|
148
160
|
* If already connected, it will resolve immediately.
|
|
161
|
+
* Returns immediately if using a custom peer.
|
|
149
162
|
*/
|
|
150
163
|
waitForConnection() {
|
|
151
|
-
return wsPeer
|
|
164
|
+
return wsPeer?.waitUntilConnected() ?? Promise.resolve();
|
|
152
165
|
},
|
|
153
166
|
subscribeToConnectionChange(listener: (connected: boolean) => void) {
|
|
167
|
+
if (!wsPeer) {
|
|
168
|
+
// For custom peers, immediately notify as connected
|
|
169
|
+
listener(true);
|
|
170
|
+
return () => {};
|
|
171
|
+
}
|
|
172
|
+
|
|
154
173
|
wsPeer.subscribe(listener);
|
|
155
174
|
|
|
156
175
|
return () => {
|