jazz-tools 0.18.17 → 0.18.19
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__/jazz.class.svelte.d.ts +14 -0
- package/.svelte-kit/__package__/jazz.class.svelte.d.ts.map +1 -1
- package/.svelte-kit/__package__/jazz.class.svelte.js +37 -0
- package/.svelte-kit/__package__/testing.d.ts +1 -1
- package/.svelte-kit/__package__/testing.d.ts.map +1 -1
- package/.svelte-kit/__package__/testing.js +1 -1
- package/.svelte-kit/__package__/tests/TestConnectionStatus.svelte +8 -0
- package/.svelte-kit/__package__/tests/TestConnectionStatus.svelte.d.ts +27 -0
- package/.svelte-kit/__package__/tests/TestConnectionStatus.svelte.d.ts.map +1 -0
- package/.svelte-kit/__package__/tests/sync-connection-status.svelte.test.d.ts +2 -0
- package/.svelte-kit/__package__/tests/sync-connection-status.svelte.test.d.ts.map +1 -0
- package/.svelte-kit/__package__/tests/sync-connection-status.svelte.test.js +47 -0
- package/.turbo/turbo-build.log +57 -57
- package/CHANGELOG.md +27 -0
- package/dist/better-auth/auth/client.d.ts.map +1 -1
- package/dist/better-auth/auth/client.js +3 -2
- package/dist/better-auth/auth/client.js.map +1 -1
- package/dist/browser/BrowserContextManager.d.ts +4 -0
- package/dist/browser/BrowserContextManager.d.ts.map +1 -1
- package/dist/browser/createBrowserContext.d.ts +4 -0
- package/dist/browser/createBrowserContext.d.ts.map +1 -1
- package/dist/browser/index.js +36 -4
- package/dist/browser/index.js.map +1 -1
- package/dist/{chunk-OTWWOZMB.js → chunk-RN3Y24WX.js} +9 -4
- package/dist/chunk-RN3Y24WX.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/react/hooks.d.ts +1 -1
- package/dist/react/hooks.d.ts.map +1 -1
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +4 -2
- package/dist/react/index.js.map +1 -1
- package/dist/react-core/hooks.d.ts +26 -0
- package/dist/react-core/hooks.d.ts.map +1 -1
- package/dist/react-core/index.js +16 -1
- package/dist/react-core/index.js.map +1 -1
- package/dist/react-core/testing.d.ts +1 -1
- package/dist/react-core/testing.d.ts.map +1 -1
- package/dist/react-core/testing.js +3 -1
- package/dist/react-core/testing.js.map +1 -1
- package/dist/react-core/tests/useSyncConnectionStatus.test.d.ts +2 -0
- package/dist/react-core/tests/useSyncConnectionStatus.test.d.ts.map +1 -0
- package/dist/react-native-core/ReactNativeContextManager.d.ts +4 -0
- package/dist/react-native-core/ReactNativeContextManager.d.ts.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 +38 -6
- package/dist/react-native-core/index.js.map +1 -1
- package/dist/react-native-core/platform.d.ts +4 -0
- package/dist/react-native-core/platform.d.ts.map +1 -1
- package/dist/svelte/jazz.class.svelte.d.ts +14 -0
- package/dist/svelte/jazz.class.svelte.d.ts.map +1 -1
- package/dist/svelte/jazz.class.svelte.js +37 -0
- package/dist/svelte/testing.d.ts +1 -1
- package/dist/svelte/testing.d.ts.map +1 -1
- package/dist/svelte/testing.js +1 -1
- package/dist/svelte/tests/TestConnectionStatus.svelte +8 -0
- package/dist/svelte/tests/TestConnectionStatus.svelte.d.ts +27 -0
- package/dist/svelte/tests/TestConnectionStatus.svelte.d.ts.map +1 -0
- package/dist/svelte/tests/sync-connection-status.svelte.test.d.ts +2 -0
- package/dist/svelte/tests/sync-connection-status.svelte.test.d.ts.map +1 -0
- package/dist/svelte/tests/sync-connection-status.svelte.test.js +47 -0
- package/dist/testing.js +34 -4
- package/dist/testing.js.map +1 -1
- package/dist/tools/coValues/coMap.d.ts +2 -2
- package/dist/tools/coValues/coMap.d.ts.map +1 -1
- package/dist/tools/implementation/ContextManager.d.ts +4 -0
- package/dist/tools/implementation/ContextManager.d.ts.map +1 -1
- package/dist/tools/subscribe/SubscriptionScope.d.ts.map +1 -1
- package/dist/tools/testing.d.ts +8 -0
- package/dist/tools/testing.d.ts.map +1 -1
- package/dist/tools/types.d.ts +4 -0
- package/dist/tools/types.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/better-auth/auth/client.ts +3 -2
- package/src/better-auth/auth/tests/client.test.ts +22 -0
- package/src/browser/createBrowserContext.ts +34 -4
- package/src/react/hooks.tsx +1 -0
- package/src/react/index.ts +1 -0
- package/src/react-core/hooks.ts +42 -0
- package/src/react-core/testing.tsx +1 -0
- package/src/react-core/tests/useAccountWithSelector.test.ts +98 -2
- package/src/react-core/tests/useSyncConnectionStatus.test.ts +48 -0
- package/src/react-native-core/hooks.tsx +1 -0
- package/src/react-native-core/platform.ts +32 -4
- package/src/svelte/jazz.class.svelte.ts +44 -0
- package/src/svelte/testing.ts +1 -0
- package/src/svelte/tests/TestConnectionStatus.svelte +8 -0
- package/src/svelte/tests/sync-connection-status.svelte.test.ts +61 -0
- package/src/tools/coValues/coMap.ts +5 -5
- package/src/tools/implementation/ContextManager.ts +8 -0
- package/src/tools/subscribe/SubscriptionScope.ts +5 -1
- package/src/tools/subscribe/index.ts +1 -1
- package/src/tools/testing.ts +29 -0
- package/src/tools/tests/ContextManager.test.ts +2 -2
- package/src/tools/tests/coMap.test.ts +68 -2
- package/src/tools/tests/subscribe.test.ts +42 -4
- package/src/tools/types.ts +4 -0
- package/dist/chunk-OTWWOZMB.js.map +0 -1
@@ -1,7 +1,7 @@
|
|
1
1
|
// @vitest-environment happy-dom
|
2
2
|
|
3
|
-
import { Account, RefsToResolve, co, z } from "jazz-tools";
|
4
|
-
import { beforeEach, describe, expect, it } from "vitest";
|
3
|
+
import { Account, RefsToResolve, co, z, Group } from "jazz-tools";
|
4
|
+
import { assert, beforeEach, describe, expect, it } from "vitest";
|
5
5
|
import { useAccountWithSelector, useJazzContextManager } from "../hooks.js";
|
6
6
|
import { useIsAuthenticated } from "../index.js";
|
7
7
|
import {
|
@@ -408,4 +408,100 @@ describe("useAccountWithSelector", () => {
|
|
408
408
|
|
409
409
|
expect(result.current.result).toEqual("initial-suffix2");
|
410
410
|
});
|
411
|
+
|
412
|
+
it("should work with branches - create branch, edit and merge", async () => {
|
413
|
+
const AccountRoot = co.map({
|
414
|
+
name: z.string(),
|
415
|
+
age: z.number(),
|
416
|
+
email: z.string(),
|
417
|
+
});
|
418
|
+
|
419
|
+
const AccountSchema = co
|
420
|
+
.account({
|
421
|
+
root: AccountRoot,
|
422
|
+
profile: co.profile(),
|
423
|
+
})
|
424
|
+
.withMigration((account, creationProps) => {
|
425
|
+
if (!account.$jazz.refs.root) {
|
426
|
+
account.$jazz.set("root", {
|
427
|
+
name: "John Doe",
|
428
|
+
age: 30,
|
429
|
+
email: "john@example.com",
|
430
|
+
});
|
431
|
+
}
|
432
|
+
});
|
433
|
+
|
434
|
+
const account = await createJazzTestAccount({
|
435
|
+
AccountSchema,
|
436
|
+
isCurrentActiveAccount: true,
|
437
|
+
});
|
438
|
+
|
439
|
+
const group = Group.create();
|
440
|
+
group.addMember("everyone", "writer");
|
441
|
+
const { result } = renderHook(
|
442
|
+
() => {
|
443
|
+
const branchAccountRoot = useAccountWithSelector(AccountSchema, {
|
444
|
+
resolve: {
|
445
|
+
root: true,
|
446
|
+
},
|
447
|
+
select: (account) => account?.root,
|
448
|
+
unstable_branch: { name: "feature-branch" },
|
449
|
+
});
|
450
|
+
|
451
|
+
const mainAccountRoot = useAccountWithSelector(AccountSchema, {
|
452
|
+
resolve: {
|
453
|
+
root: true,
|
454
|
+
},
|
455
|
+
select: (account) => account?.root,
|
456
|
+
});
|
457
|
+
|
458
|
+
return {
|
459
|
+
branchAccountRoot,
|
460
|
+
mainAccountRoot,
|
461
|
+
};
|
462
|
+
},
|
463
|
+
{
|
464
|
+
account,
|
465
|
+
},
|
466
|
+
);
|
467
|
+
|
468
|
+
await act(async () => {
|
469
|
+
// Wait for the account to be loaded
|
470
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
471
|
+
});
|
472
|
+
|
473
|
+
expect(result.current).not.toBeNull();
|
474
|
+
|
475
|
+
const branchAccountRoot = result.current.branchAccountRoot;
|
476
|
+
const mainAccountRoot = result.current.mainAccountRoot;
|
477
|
+
|
478
|
+
assert(branchAccountRoot);
|
479
|
+
assert(mainAccountRoot);
|
480
|
+
|
481
|
+
act(() => {
|
482
|
+
branchAccountRoot.$jazz.applyDiff({
|
483
|
+
name: "John Smith",
|
484
|
+
age: 31,
|
485
|
+
email: "john.smith@example.com",
|
486
|
+
});
|
487
|
+
});
|
488
|
+
|
489
|
+
// Verify the branch has the changes
|
490
|
+
expect(branchAccountRoot.name).toBe("John Smith");
|
491
|
+
expect(branchAccountRoot.age).toBe(31);
|
492
|
+
expect(branchAccountRoot.email).toBe("john.smith@example.com");
|
493
|
+
|
494
|
+
// Verify the original is unchanged
|
495
|
+
expect(mainAccountRoot.name).toBe("John Doe");
|
496
|
+
expect(mainAccountRoot.age).toBe(30);
|
497
|
+
expect(mainAccountRoot.email).toBe("john@example.com");
|
498
|
+
|
499
|
+
// Merge the branch back
|
500
|
+
branchAccountRoot.$jazz.unstable_merge();
|
501
|
+
|
502
|
+
// Verify the original now has the merged changes
|
503
|
+
expect(mainAccountRoot.name).toBe("John Smith");
|
504
|
+
expect(mainAccountRoot.age).toBe(31);
|
505
|
+
expect(mainAccountRoot.email).toBe("john.smith@example.com");
|
506
|
+
});
|
411
507
|
});
|
@@ -0,0 +1,48 @@
|
|
1
|
+
// @vitest-environment happy-dom
|
2
|
+
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
4
|
+
import { useSyncConnectionStatus } from "../hooks";
|
5
|
+
import {
|
6
|
+
createJazzTestAccount,
|
7
|
+
setupJazzTestSync,
|
8
|
+
MockConnectionStatus,
|
9
|
+
} from "../testing";
|
10
|
+
import { act, cleanup, renderHook } from "./testUtils";
|
11
|
+
|
12
|
+
describe("useSyncConnectionStatus", () => {
|
13
|
+
beforeEach(async () => {
|
14
|
+
await setupJazzTestSync();
|
15
|
+
await createJazzTestAccount({
|
16
|
+
isCurrentActiveAccount: true,
|
17
|
+
});
|
18
|
+
});
|
19
|
+
|
20
|
+
afterEach(() => {
|
21
|
+
vi.clearAllMocks();
|
22
|
+
cleanup();
|
23
|
+
});
|
24
|
+
|
25
|
+
it("should return true by default in the test environment", () => {
|
26
|
+
const { result } = renderHook(() => useSyncConnectionStatus());
|
27
|
+
|
28
|
+
expect(result.current).toBe(true);
|
29
|
+
});
|
30
|
+
|
31
|
+
it("should handle updates", async () => {
|
32
|
+
const { result } = renderHook(() => useSyncConnectionStatus());
|
33
|
+
|
34
|
+
expect(result.current).toBe(true);
|
35
|
+
|
36
|
+
act(() => {
|
37
|
+
MockConnectionStatus.setIsConnected(false);
|
38
|
+
});
|
39
|
+
|
40
|
+
expect(result.current).toBe(false);
|
41
|
+
|
42
|
+
act(() => {
|
43
|
+
MockConnectionStatus.setIsConnected(true);
|
44
|
+
});
|
45
|
+
|
46
|
+
expect(result.current).toBe(true);
|
47
|
+
});
|
48
|
+
});
|
@@ -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 {
|
110
|
-
|
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 {
|
153
|
-
|
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
|
+
}
|
package/src/svelte/testing.ts
CHANGED
@@ -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
|
+
});
|
@@ -34,6 +34,7 @@ import {
|
|
34
34
|
CoValueBase,
|
35
35
|
CoValueJazzApi,
|
36
36
|
ItemsSym,
|
37
|
+
NotNull,
|
37
38
|
Ref,
|
38
39
|
RegisteredSchemas,
|
39
40
|
SchemaInit,
|
@@ -760,11 +761,10 @@ class CoMapJazzApi<M extends CoMap> extends CoValueJazzApi<M> {
|
|
760
761
|
? Key
|
761
762
|
: never]?: RefIfCoValue<M[Key]>;
|
762
763
|
} & {
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
: never]: RefIfCoValue<M[Key]>;
|
764
|
+
// Non-loaded CoValue refs (i.e. refs with type CoValue | null) are still required refs
|
765
|
+
[Key in CoKeys<M> as NotNull<M[Key]> extends CoValue
|
766
|
+
? Key
|
767
|
+
: never]: RefIfCoValue<M[Key]>;
|
768
768
|
}
|
769
769
|
> {
|
770
770
|
return makeRefs<CoKeys<this>>(
|
@@ -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 (
|
82
|
+
if (
|
83
|
+
!this.migrated &&
|
84
|
+
value !== "unavailable" &&
|
85
|
+
!value.core.verified.isStreaming()
|
86
|
+
) {
|
83
87
|
if (this.migrating) {
|
84
88
|
return;
|
85
89
|
}
|
@@ -18,7 +18,7 @@ export function getSubscriptionScope<D extends CoValue>(value: D) {
|
|
18
18
|
});
|
19
19
|
|
20
20
|
Object.defineProperty(value.$jazz, "_subscriptionScope", {
|
21
|
-
value:
|
21
|
+
value: newSubscriptionScope,
|
22
22
|
writable: false,
|
23
23
|
enumerable: false,
|
24
24
|
configurable: false,
|
package/src/tools/testing.ts
CHANGED
@@ -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();
|
@@ -1150,6 +1150,30 @@ describe("CoMap resolution", async () => {
|
|
1150
1150
|
});
|
1151
1151
|
});
|
1152
1152
|
|
1153
|
+
test("obtaining coMap refs", async () => {
|
1154
|
+
const Dog = co.map({
|
1155
|
+
name: z.string().optional(),
|
1156
|
+
breed: z.string(),
|
1157
|
+
owner: co.plainText(),
|
1158
|
+
get parent() {
|
1159
|
+
return co.optional(Dog);
|
1160
|
+
},
|
1161
|
+
});
|
1162
|
+
|
1163
|
+
const dog = Dog.create({
|
1164
|
+
name: "Rex",
|
1165
|
+
breed: "Labrador",
|
1166
|
+
owner: "John",
|
1167
|
+
parent: { name: "Fido", breed: "Labrador", owner: "Jane" },
|
1168
|
+
});
|
1169
|
+
|
1170
|
+
const refs = dog.$jazz.refs;
|
1171
|
+
|
1172
|
+
expect(Object.keys(refs)).toEqual(["owner", "parent"]);
|
1173
|
+
expect(refs.owner.id).toEqual(dog.owner.$jazz.id);
|
1174
|
+
expect(refs.parent?.id).toEqual(dog.parent!.$jazz.id);
|
1175
|
+
});
|
1176
|
+
|
1153
1177
|
test("accessing the value refs", async () => {
|
1154
1178
|
const Dog = co.map({
|
1155
1179
|
name: z.string(),
|
@@ -1181,9 +1205,9 @@ describe("CoMap resolution", async () => {
|
|
1181
1205
|
|
1182
1206
|
assert(loadedPerson);
|
1183
1207
|
|
1184
|
-
expect(loadedPerson.$jazz.refs.dog
|
1208
|
+
expect(loadedPerson.$jazz.refs.dog.id).toBe(person.dog.$jazz.id);
|
1185
1209
|
|
1186
|
-
const dog = await loadedPerson.$jazz.refs.dog
|
1210
|
+
const dog = await loadedPerson.$jazz.refs.dog.load();
|
1187
1211
|
|
1188
1212
|
assert(dog);
|
1189
1213
|
|
@@ -2510,6 +2534,48 @@ describe("CoMap migration", () => {
|
|
2510
2534
|
expect(spy).toHaveBeenCalledTimes(1);
|
2511
2535
|
});
|
2512
2536
|
|
2537
|
+
test("should run only when the value is fully loaded", async () => {
|
2538
|
+
await setupJazzTestSync({
|
2539
|
+
asyncPeers: true,
|
2540
|
+
});
|
2541
|
+
await createJazzTestAccount({
|
2542
|
+
isCurrentActiveAccount: true,
|
2543
|
+
creationProps: { name: "Hermes Puggington" },
|
2544
|
+
});
|
2545
|
+
|
2546
|
+
const migration = vi.fn();
|
2547
|
+
const Person = co
|
2548
|
+
.map({
|
2549
|
+
name: z.string(),
|
2550
|
+
update: z.number(),
|
2551
|
+
})
|
2552
|
+
.withMigration((person) => {
|
2553
|
+
migration(person.update);
|
2554
|
+
});
|
2555
|
+
|
2556
|
+
const person = Person.create({
|
2557
|
+
name: "Bob",
|
2558
|
+
update: 1,
|
2559
|
+
});
|
2560
|
+
|
2561
|
+
// Pump the value to reach streaming
|
2562
|
+
for (let i = 0; i <= 300; i++) {
|
2563
|
+
person.$jazz.raw.assign({
|
2564
|
+
name: "1".repeat(1024),
|
2565
|
+
update: i,
|
2566
|
+
});
|
2567
|
+
}
|
2568
|
+
|
2569
|
+
// Upload and unmount, to force the streaming download
|
2570
|
+
await person.$jazz.waitForSync();
|
2571
|
+
person.$jazz.raw.core.unmount();
|
2572
|
+
|
2573
|
+
// Load the value and expect the migration to run only once
|
2574
|
+
await Person.load(person.$jazz.id);
|
2575
|
+
expect(migration).toHaveBeenCalledTimes(1);
|
2576
|
+
expect(migration).toHaveBeenCalledWith(300);
|
2577
|
+
});
|
2578
|
+
|
2513
2579
|
test("should not break recursive schemas", async () => {
|
2514
2580
|
const PersonV1 = co.map({
|
2515
2581
|
name: z.string(),
|
@@ -20,6 +20,7 @@ import {
|
|
20
20
|
setupJazzTestSync,
|
21
21
|
} from "../testing.js";
|
22
22
|
import { setupAccount, waitFor } from "./utils.js";
|
23
|
+
import { getSubscriptionScope } from "../subscribe/index.js";
|
23
24
|
|
24
25
|
cojsonInternals.setCoValueLoadingRetryDelay(300);
|
25
26
|
|
@@ -559,10 +560,7 @@ describe("subscribeToCoValue", () => {
|
|
559
560
|
assert(result);
|
560
561
|
|
561
562
|
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);
|
563
|
+
expect(updateFn).toHaveBeenCalledTimes(1);
|
566
564
|
});
|
567
565
|
|
568
566
|
it("should handle undefined values in lists with required refs", async () => {
|
@@ -1281,3 +1279,43 @@ describe("subscribeToCoValue", () => {
|
|
1281
1279
|
expect(result.data[chunks]).toBe("new entry");
|
1282
1280
|
});
|
1283
1281
|
});
|
1282
|
+
|
1283
|
+
describe("getSubscriptionScope", () => {
|
1284
|
+
const Person = co.map({
|
1285
|
+
name: z.string(),
|
1286
|
+
});
|
1287
|
+
let person: co.output<typeof Person>;
|
1288
|
+
|
1289
|
+
beforeEach(async () => {
|
1290
|
+
await createJazzTestAccount({
|
1291
|
+
isCurrentActiveAccount: true,
|
1292
|
+
creationProps: { name: "Hermes Puggington" },
|
1293
|
+
});
|
1294
|
+
|
1295
|
+
person = Person.create({ name: "John" });
|
1296
|
+
});
|
1297
|
+
|
1298
|
+
describe("when the coValue doesn't have a subscription scope", () => {
|
1299
|
+
it("creates a new subscription scope", () => {
|
1300
|
+
expect(person.$jazz._subscriptionScope).toBeUndefined();
|
1301
|
+
const subscriptionScope = getSubscriptionScope(person);
|
1302
|
+
expect(subscriptionScope).toBeDefined();
|
1303
|
+
});
|
1304
|
+
|
1305
|
+
it("updates the subscription scope in the coValue", () => {
|
1306
|
+
const subscriptionScope = getSubscriptionScope(person);
|
1307
|
+
expect(person.$jazz._subscriptionScope).toBeDefined();
|
1308
|
+
expect(person.$jazz._subscriptionScope).toBe(subscriptionScope);
|
1309
|
+
});
|
1310
|
+
});
|
1311
|
+
|
1312
|
+
describe("when the coValue already has a subscription scope", () => {
|
1313
|
+
it("returns that subscription scope", async () => {
|
1314
|
+
const loadedPerson = await Person.load(person.$jazz.id);
|
1315
|
+
assert(loadedPerson);
|
1316
|
+
const subscriptionScope = loadedPerson.$jazz._subscriptionScope;
|
1317
|
+
expect(subscriptionScope).toBeDefined();
|
1318
|
+
expect(getSubscriptionScope(loadedPerson)).toBe(subscriptionScope);
|
1319
|
+
});
|
1320
|
+
});
|
1321
|
+
});
|