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/dist/index.js CHANGED
@@ -50,7 +50,7 @@ import {
50
50
  subscribeToCoValue,
51
51
  unstable_loadUnique,
52
52
  zodReExport_exports
53
- } from "./chunk-Q5RNSSUM.js";
53
+ } from "./chunk-XQ27DEA5.js";
54
54
  import {
55
55
  createSSRJazzAgent
56
56
  } from "./chunk-K4D7IMFM.js";
package/dist/testing.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  coValueClassFromCoValueClassOrSchema,
8
8
  createAnonymousJazzContext,
9
9
  createJazzContext
10
- } from "./chunk-Q5RNSSUM.js";
10
+ } from "./chunk-XQ27DEA5.js";
11
11
  import "./chunk-ZQWSQH6L.js";
12
12
 
13
13
  // src/tools/testing.ts
@@ -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;AAanD,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;IA8DhD;;OAEG;IACH,KAAK,IAAI,IAAI;CAiBd"}
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":"AA+CA,wBAAsB,kBAAkB,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,6CAU3E;AAED,wBAAgB,SAAS,CAAC,aAAa,CAAC,EAAE,MAAM,UAU/C"}
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.3",
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.3",
217
- "cojson-storage-indexeddb": "0.20.3",
218
- "cojson-transport-ws": "0.20.3"
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(getDbPath(filename)),
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
- const dbPath = defaultDbPath ?? join(tmpdir(), `test-${randomUUID()}.db`);
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
  }