jazz-tools 0.20.3 → 0.20.4
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/.turbo/turbo-build.log +48 -48
- package/CHANGELOG.md +9 -0
- package/dist/{chunk-Q5RNSSUM.js → chunk-XQ27DEA5.js} +8 -2
- package/dist/{chunk-Q5RNSSUM.js.map → chunk-XQ27DEA5.js.map} +1 -1
- package/dist/index.js +1 -1
- package/dist/testing.js +1 -1
- package/dist/tools/subscribe/SubscriptionCache.d.ts.map +1 -1
- package/dist/tools/tests/testStorage.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/react-core/tests/useCoState.test.ts +135 -0
- package/src/tools/subscribe/SubscriptionCache.ts +10 -1
- package/src/tools/tests/testStorage.ts +12 -10
package/dist/index.js
CHANGED
package/dist/testing.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SubscriptionCache.d.ts","sourceRoot":"","sources":["../../../src/tools/subscribe/SubscriptionCache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,KAAK,EAEV,oBAAoB,EACpB,MAAM,EAGN,YAAY,EACb,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"SubscriptionCache.d.ts","sourceRoot":"","sources":["../../../src/tools/subscribe/SubscriptionCache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,KAAK,EAEV,oBAAoB,EACpB,MAAM,EAGN,YAAY,EACb,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAqBnD,qBAAa,iBAAiB;IAE5B,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,cAAc,CAAS;gBAEnB,cAAc,GAAE,MAAa;IAKzC;;OAEG;IACH,OAAO,CAAC,QAAQ;IAIhB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IASxB;;OAEG;IACH,OAAO,CAAC,YAAY;IA+BpB;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAsBzB;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAY9B;;OAEG;IACH,OAAO,CAAC,eAAe;IASvB;;OAEG;IACH,OAAO,CAAC,aAAa;IAOrB;;OAEG;IACH,OAAO,CAAC,YAAY;IA2BpB;;OAEG;IACH,WAAW,CAAC,CAAC,SAAS,oBAAoB,EACxC,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,CAAC,EACT,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,EACxB,SAAS,CAAC,EAAE,OAAO,EACnB,oBAAoB,CAAC,EAAE,OAAO,EAC9B,MAAM,CAAC,EAAE,gBAAgB,GACxB,iBAAiB,CAAC,MAAM,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;IA+DhD;;OAEG;IACH,KAAK,IAAI,IAAI;CAiBd"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testStorage.d.ts","sourceRoot":"","sources":["../../../src/tools/tests/testStorage.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"testStorage.d.ts","sourceRoot":"","sources":["../../../src/tools/tests/testStorage.ts"],"names":[],"mappings":"AAuDA,wBAAsB,kBAAkB,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,6CAY3E;AAED,wBAAgB,SAAS,CAAC,aAAa,CAAC,EAAE,MAAM,UAE/C"}
|
package/package.json
CHANGED
|
@@ -196,7 +196,7 @@
|
|
|
196
196
|
},
|
|
197
197
|
"type": "module",
|
|
198
198
|
"license": "MIT",
|
|
199
|
-
"version": "0.20.
|
|
199
|
+
"version": "0.20.4",
|
|
200
200
|
"dependencies": {
|
|
201
201
|
"@manuscripts/prosemirror-recreate-steps": "^0.1.4",
|
|
202
202
|
"@scure/base": "1.2.1",
|
|
@@ -213,9 +213,9 @@
|
|
|
213
213
|
"prosemirror-transform": "^1.9.0",
|
|
214
214
|
"use-sync-external-store": "^1.5.0",
|
|
215
215
|
"zod": "4.1.11",
|
|
216
|
-
"cojson": "0.20.
|
|
217
|
-
"cojson-storage-indexeddb": "0.20.
|
|
218
|
-
"cojson-transport-ws": "0.20.
|
|
216
|
+
"cojson": "0.20.4",
|
|
217
|
+
"cojson-storage-indexeddb": "0.20.4",
|
|
218
|
+
"cojson-transport-ws": "0.20.4"
|
|
219
219
|
},
|
|
220
220
|
"devDependencies": {
|
|
221
221
|
"@scure/bip39": "^1.3.0",
|
|
@@ -890,4 +890,139 @@ describe("useCoState", () => {
|
|
|
890
890
|
expect(result.current.person.age).toBe(30);
|
|
891
891
|
expect(result.current.person.email).toBe("john@example.com");
|
|
892
892
|
});
|
|
893
|
+
|
|
894
|
+
it("should render without infinite re-renders when accessing resolved schema", async () => {
|
|
895
|
+
const account = await createJazzTestAccount({
|
|
896
|
+
isCurrentActiveAccount: true,
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
// Minimal schema with a nested optional co-value
|
|
900
|
+
const ServerState = co.map({
|
|
901
|
+
status: z.string(),
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
const Organization = co
|
|
905
|
+
.map({
|
|
906
|
+
name: z.string(),
|
|
907
|
+
serverState: co.optional(ServerState),
|
|
908
|
+
})
|
|
909
|
+
.resolved({ serverState: true });
|
|
910
|
+
|
|
911
|
+
const org = Organization.create({
|
|
912
|
+
name: "Test Org",
|
|
913
|
+
serverState: ServerState.create({ status: "active" }),
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
let renderCount = 0;
|
|
917
|
+
|
|
918
|
+
const { result } = renderHook(
|
|
919
|
+
() => {
|
|
920
|
+
renderCount++;
|
|
921
|
+
const orgState = useCoState(Organization, org.$jazz.id);
|
|
922
|
+
|
|
923
|
+
// Access the unresolved nested co-value
|
|
924
|
+
if (orgState.$isLoaded) {
|
|
925
|
+
// This should not cause infinite re-renders
|
|
926
|
+
const _ = orgState.serverState;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
return orgState;
|
|
930
|
+
},
|
|
931
|
+
{
|
|
932
|
+
account,
|
|
933
|
+
},
|
|
934
|
+
);
|
|
935
|
+
|
|
936
|
+
await waitFor(() => {
|
|
937
|
+
assertLoaded(result.current);
|
|
938
|
+
expect(result.current.name).toBe("Test Org");
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
// Wait a bit to ensure no additional re-renders occur
|
|
942
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
943
|
+
|
|
944
|
+
// Should not have excessive re-renders
|
|
945
|
+
expect(renderCount).toBeLessThan(10);
|
|
946
|
+
});
|
|
947
|
+
|
|
948
|
+
it("should render without infinite re-renders when accessing unresolved nested co-value", async () => {
|
|
949
|
+
const account = await createJazzTestAccount({
|
|
950
|
+
isCurrentActiveAccount: true,
|
|
951
|
+
});
|
|
952
|
+
|
|
953
|
+
// Minimal schema with a nested optional co-value
|
|
954
|
+
const ServerState = co.map({
|
|
955
|
+
status: z.string(),
|
|
956
|
+
});
|
|
957
|
+
|
|
958
|
+
const Organization = co.map({
|
|
959
|
+
name: z.string(),
|
|
960
|
+
serverState: co.optional(ServerState),
|
|
961
|
+
});
|
|
962
|
+
|
|
963
|
+
const org = Organization.create({
|
|
964
|
+
name: "Test Org",
|
|
965
|
+
serverState: ServerState.create({ status: "active" }),
|
|
966
|
+
});
|
|
967
|
+
|
|
968
|
+
let renderCount = 0;
|
|
969
|
+
|
|
970
|
+
const { result } = renderHook(
|
|
971
|
+
() => {
|
|
972
|
+
renderCount++;
|
|
973
|
+
const orgState = useCoState(Organization, org.$jazz.id, {
|
|
974
|
+
resolve: {},
|
|
975
|
+
});
|
|
976
|
+
|
|
977
|
+
// Access the unresolved nested co-value
|
|
978
|
+
if (orgState.$isLoaded) {
|
|
979
|
+
// This should not cause infinite re-renders
|
|
980
|
+
const _ = orgState.serverState;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
return orgState;
|
|
984
|
+
},
|
|
985
|
+
{
|
|
986
|
+
account,
|
|
987
|
+
},
|
|
988
|
+
);
|
|
989
|
+
|
|
990
|
+
await waitFor(() => {
|
|
991
|
+
assertLoaded(result.current);
|
|
992
|
+
expect(result.current.name).toBe("Test Org");
|
|
993
|
+
});
|
|
994
|
+
|
|
995
|
+
// Wait a bit to ensure no additional re-renders occur
|
|
996
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
997
|
+
|
|
998
|
+
// Should not have excessive re-renders
|
|
999
|
+
expect(renderCount).toBeLessThan(10);
|
|
1000
|
+
});
|
|
1001
|
+
|
|
1002
|
+
it("should not cause stack overflow when cloning resolve queries with circular references", async () => {
|
|
1003
|
+
const account = await createJazzTestAccount({
|
|
1004
|
+
isCurrentActiveAccount: true,
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
const TestMap = co.map({
|
|
1008
|
+
value: z.string(),
|
|
1009
|
+
});
|
|
1010
|
+
|
|
1011
|
+
const map = TestMap.create({
|
|
1012
|
+
value: "test",
|
|
1013
|
+
});
|
|
1014
|
+
|
|
1015
|
+
// Create a resolve query with circular reference
|
|
1016
|
+
const circularResolve: Record<string, any> = { value: true };
|
|
1017
|
+
circularResolve.self = circularResolve;
|
|
1018
|
+
|
|
1019
|
+
// This should not throw a stack overflow error
|
|
1020
|
+
const { result } = renderHook(
|
|
1021
|
+
() => useCoState(TestMap, map.$jazz.id, { resolve: circularResolve }),
|
|
1022
|
+
{ account },
|
|
1023
|
+
);
|
|
1024
|
+
|
|
1025
|
+
assertLoaded(result.current);
|
|
1026
|
+
expect(result.current.value).toBe("test");
|
|
1027
|
+
});
|
|
893
1028
|
});
|
|
@@ -12,6 +12,14 @@ import { SubscriptionScope } from "./SubscriptionScope.js";
|
|
|
12
12
|
import type { BranchDefinition } from "./types.js";
|
|
13
13
|
import { isEqualRefsToResolve } from "./utils.js";
|
|
14
14
|
|
|
15
|
+
function copyResolve(resolve: RefsToResolve<any>): RefsToResolve<any> {
|
|
16
|
+
if (typeof resolve !== "object" || resolve === null) {
|
|
17
|
+
return resolve;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return { ...resolve };
|
|
21
|
+
}
|
|
22
|
+
|
|
15
23
|
interface CacheEntry {
|
|
16
24
|
subscriptionScope: SubscriptionScope<any>;
|
|
17
25
|
schema: CoValueClassOrSchema;
|
|
@@ -233,10 +241,11 @@ export class SubscriptionCache {
|
|
|
233
241
|
};
|
|
234
242
|
|
|
235
243
|
// Create cache entry with initial subscriber count (starts at 0)
|
|
244
|
+
// Clone resolve to prevent mutation by SubscriptionScope.subscribeToKey from affecting cache lookups
|
|
236
245
|
const entry: CacheEntry = {
|
|
237
246
|
subscriptionScope,
|
|
238
247
|
schema,
|
|
239
|
-
resolve,
|
|
248
|
+
resolve: copyResolve(resolve),
|
|
240
249
|
branch,
|
|
241
250
|
subscriberCount: subscriptionScope.subscribers.size,
|
|
242
251
|
unsubscribeFromScope: subscriptionScope.onSubscriberChange(
|
|
@@ -45,26 +45,28 @@ class LibSQLSqliteAsyncDriver implements SQLiteDatabaseDriverAsync {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
function deleteDb(dbPath: string) {
|
|
49
|
+
try {
|
|
50
|
+
unlinkSync(dbPath);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error(error);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
48
56
|
export async function createAsyncStorage({ filename }: { filename?: string }) {
|
|
57
|
+
const dbPath = getDbPath(filename);
|
|
49
58
|
const storage = await getSqliteStorageAsync(
|
|
50
|
-
new LibSQLSqliteAsyncDriver(
|
|
59
|
+
new LibSQLSqliteAsyncDriver(dbPath),
|
|
51
60
|
);
|
|
52
61
|
|
|
53
62
|
onTestFinished(async () => {
|
|
54
63
|
await storage.close();
|
|
64
|
+
deleteDb(dbPath);
|
|
55
65
|
});
|
|
56
66
|
|
|
57
67
|
return storage;
|
|
58
68
|
}
|
|
59
69
|
|
|
60
70
|
export function getDbPath(defaultDbPath?: string) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (!defaultDbPath) {
|
|
64
|
-
onTestFinished(() => {
|
|
65
|
-
unlinkSync(dbPath);
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return dbPath;
|
|
71
|
+
return defaultDbPath ?? join(tmpdir(), `test-${randomUUID()}.db`);
|
|
70
72
|
}
|