jazz-tools 0.19.3 → 0.19.5

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 (75) hide show
  1. package/.svelte-kit/__package__/jazz.class.svelte.d.ts +2 -2
  2. package/.svelte-kit/__package__/jazz.class.svelte.d.ts.map +1 -1
  3. package/.svelte-kit/__package__/jazz.class.svelte.js +15 -17
  4. package/.turbo/turbo-build.log +65 -65
  5. package/CHANGELOG.md +23 -0
  6. package/dist/{chunk-JPWM4CS2.js → chunk-DFFRRRRF.js} +137 -77
  7. package/dist/chunk-DFFRRRRF.js.map +1 -0
  8. package/dist/index.js +14 -7
  9. package/dist/index.js.map +1 -1
  10. package/dist/inspector/{custom-element-3JAYHXWQ.js → custom-element-P76EIWEV.js} +301 -142
  11. package/dist/inspector/{custom-element-3JAYHXWQ.js.map → custom-element-P76EIWEV.js.map} +1 -1
  12. package/dist/inspector/index.js +281 -122
  13. package/dist/inspector/index.js.map +1 -1
  14. package/dist/inspector/register-custom-element.js +1 -1
  15. package/dist/inspector/tests/viewer/co-plain-text-view.test.d.ts +2 -0
  16. package/dist/inspector/tests/viewer/co-plain-text-view.test.d.ts.map +1 -0
  17. package/dist/inspector/utils/history.d.ts +5 -1
  18. package/dist/inspector/utils/history.d.ts.map +1 -1
  19. package/dist/inspector/viewer/co-plain-text-view.d.ts +4 -2
  20. package/dist/inspector/viewer/co-plain-text-view.d.ts.map +1 -1
  21. package/dist/inspector/viewer/page.d.ts.map +1 -1
  22. package/dist/inspector/viewer/use-resolve-covalue.d.ts +0 -1
  23. package/dist/inspector/viewer/use-resolve-covalue.d.ts.map +1 -1
  24. package/dist/react-core/hooks.d.ts.map +1 -1
  25. package/dist/react-core/index.js +4 -17
  26. package/dist/react-core/index.js.map +1 -1
  27. package/dist/svelte/jazz.class.svelte.d.ts +2 -2
  28. package/dist/svelte/jazz.class.svelte.d.ts.map +1 -1
  29. package/dist/svelte/jazz.class.svelte.js +15 -17
  30. package/dist/testing.js +1 -1
  31. package/dist/tools/coValues/coFeed.d.ts.map +1 -1
  32. package/dist/tools/coValues/group.d.ts.map +1 -1
  33. package/dist/tools/coValues/interfaces.d.ts +7 -6
  34. package/dist/tools/coValues/interfaces.d.ts.map +1 -1
  35. package/dist/tools/coValues/request.d.ts.map +1 -1
  36. package/dist/tools/exports.d.ts +1 -1
  37. package/dist/tools/exports.d.ts.map +1 -1
  38. package/dist/tools/implementation/refs.d.ts +1 -1
  39. package/dist/tools/implementation/refs.d.ts.map +1 -1
  40. package/dist/tools/implementation/zodSchema/runtimeConverters/schemaFieldToCoFieldDef.d.ts +3 -1
  41. package/dist/tools/implementation/zodSchema/runtimeConverters/schemaFieldToCoFieldDef.d.ts.map +1 -1
  42. package/dist/tools/subscribe/SubscriptionScope.d.ts +5 -2
  43. package/dist/tools/subscribe/SubscriptionScope.d.ts.map +1 -1
  44. package/dist/tools/subscribe/index.d.ts +1 -1
  45. package/dist/tools/subscribe/index.d.ts.map +1 -1
  46. package/dist/tools/subscribe/types.d.ts +2 -1
  47. package/dist/tools/subscribe/types.d.ts.map +1 -1
  48. package/dist/tools/tests/SubscriptionScope.test.d.ts +2 -0
  49. package/dist/tools/tests/SubscriptionScope.test.d.ts.map +1 -0
  50. package/package.json +4 -4
  51. package/src/inspector/tests/utils/history.test.ts +233 -2
  52. package/src/inspector/tests/viewer/co-plain-text-view.test.tsx +125 -0
  53. package/src/inspector/tests/viewer/history-view.test.tsx +134 -2
  54. package/src/inspector/utils/history.ts +168 -1
  55. package/src/inspector/viewer/co-plain-text-view.tsx +102 -3
  56. package/src/inspector/viewer/history-view.tsx +5 -25
  57. package/src/inspector/viewer/page.tsx +8 -1
  58. package/src/inspector/viewer/use-resolve-covalue.ts +2 -6
  59. package/src/react-core/hooks.ts +5 -29
  60. package/src/svelte/jazz.class.svelte.ts +16 -34
  61. package/src/tools/coValues/coFeed.ts +10 -7
  62. package/src/tools/coValues/coMap.ts +10 -7
  63. package/src/tools/coValues/group.ts +6 -2
  64. package/src/tools/coValues/interfaces.ts +48 -28
  65. package/src/tools/coValues/request.ts +12 -8
  66. package/src/tools/exports.ts +1 -0
  67. package/src/tools/implementation/refs.ts +9 -17
  68. package/src/tools/implementation/zodSchema/runtimeConverters/schemaFieldToCoFieldDef.ts +62 -30
  69. package/src/tools/subscribe/SubscriptionScope.ts +38 -2
  70. package/src/tools/subscribe/index.ts +28 -13
  71. package/src/tools/subscribe/types.ts +5 -2
  72. package/src/tools/tests/SubscriptionScope.test.ts +397 -0
  73. package/src/tools/tests/deepLoading.test.ts +22 -0
  74. package/src/tools/tests/subscribe.test.ts +69 -0
  75. package/dist/chunk-JPWM4CS2.js.map +0 -1
@@ -1,4 +1,9 @@
1
- import type { CoValue, CoValueClass, RefEncoded } from "../internal.js";
1
+ import type {
2
+ CoValue,
3
+ CoValueClass,
4
+ MaybeLoaded,
5
+ RefEncoded,
6
+ } from "../internal.js";
2
7
  import { createUnloadedCoValue } from "../internal.js";
3
8
  import { SubscriptionScope } from "./SubscriptionScope.js";
4
9
  import { CoValueLoadingState } from "./types.js";
@@ -56,16 +61,22 @@ export function accessChildByKey<D extends CoValue>(
56
61
  );
57
62
  }
58
63
 
64
+ // TODO: this doesn't check the subscription tree loading state
65
+ // so if one of the children is loading, it will return the loading state
66
+ // instead of the latest loaded state
59
67
  const value = subscriptionScope.childValues.get(childId);
60
68
 
61
69
  if (value?.type === CoValueLoadingState.LOADED) {
62
70
  return value.value;
63
- } else {
64
- return createUnloadedCoValue(
65
- childId,
66
- value?.type ?? CoValueLoadingState.LOADING,
67
- );
68
71
  }
72
+
73
+ const childNode = subscriptionScope.childNodes.get(childId);
74
+
75
+ if (!childNode) {
76
+ return createUnloadedCoValue(childId, CoValueLoadingState.UNAVAILABLE);
77
+ }
78
+
79
+ return childNode.getCurrentValue();
69
80
  }
70
81
 
71
82
  /**
@@ -77,9 +88,9 @@ export function accessChildByKey<D extends CoValue>(
77
88
  * Used for refs that never change (e.g. CoFeed entries, CoMap edits)
78
89
  */
79
90
  export function accessChildById<D extends CoValue>(
80
- parent: D,
91
+ parent: CoValue,
81
92
  childId: string,
82
- schema: RefEncoded<CoValue>,
93
+ schema: RefEncoded<D>,
83
94
  ) {
84
95
  const subscriptionScope = getSubscriptionScope(parent);
85
96
 
@@ -87,12 +98,16 @@ export function accessChildById<D extends CoValue>(
87
98
 
88
99
  const value = subscriptionScope.childValues.get(childId);
89
100
 
101
+ // TODO: this doesn't check the subscription tree loading state
90
102
  if (value?.type === CoValueLoadingState.LOADED) {
91
103
  return value.value;
92
- } else {
93
- return createUnloadedCoValue(
94
- childId,
95
- value?.type ?? CoValueLoadingState.LOADING,
96
- );
97
104
  }
105
+
106
+ const childNode = subscriptionScope.childNodes.get(childId);
107
+
108
+ if (!childNode) {
109
+ return createUnloadedCoValue<D>(childId, CoValueLoadingState.UNAVAILABLE);
110
+ }
111
+
112
+ return childNode.getCurrentValue() as MaybeLoaded<D>;
98
113
  }
@@ -29,10 +29,13 @@ export const CoValueLoadingState = {
29
29
  export type CoValueLoadingState =
30
30
  (typeof CoValueLoadingState)[keyof typeof CoValueLoadingState];
31
31
 
32
+ export type CoValueErrorState =
33
+ | typeof CoValueLoadingState.UNAVAILABLE
34
+ | typeof CoValueLoadingState.UNAUTHORIZED;
35
+
32
36
  export type NotLoadedCoValueState =
33
37
  | typeof CoValueLoadingState.LOADING
34
- | typeof CoValueLoadingState.UNAUTHORIZED
35
- | typeof CoValueLoadingState.UNAVAILABLE;
38
+ | CoValueErrorState;
36
39
 
37
40
  export type SubscriptionValue<D extends CoValue, R extends RefsToResolve<D>> =
38
41
  | {
@@ -0,0 +1,397 @@
1
+ import { beforeEach, describe, expect, it } from "vitest";
2
+ import { Account, Group, co, z } from "../exports.js";
3
+ import {
4
+ CoValueLoadingState,
5
+ coValueClassFromCoValueClassOrSchema,
6
+ } from "../internal.js";
7
+ import { createJazzTestAccount, setupJazzTestSync } from "../testing.js";
8
+ import { JazzError } from "../subscribe/JazzError.js";
9
+ import { SubscriptionScope } from "../subscribe/SubscriptionScope.js";
10
+
11
+ describe("SubscriptionScope", () => {
12
+ const Person = co.map({
13
+ name: z.string(),
14
+ });
15
+
16
+ beforeEach(async () => {
17
+ await setupJazzTestSync();
18
+
19
+ await createJazzTestAccount({
20
+ isCurrentActiveAccount: true,
21
+ creationProps: { name: "Hermes Puggington" },
22
+ });
23
+ });
24
+
25
+ describe("getCurrentValue reference stability", () => {
26
+ it("returns the same reference when called multiple times with the same loading state", () => {
27
+ const person = Person.create({ name: "John" });
28
+ const node = person.$jazz.raw.core.node;
29
+ const id = person.$jazz.id;
30
+ const schema = {
31
+ ref: coValueClassFromCoValueClassOrSchema(Person),
32
+ optional: false,
33
+ };
34
+
35
+ const scope = new SubscriptionScope(node, true, id, schema);
36
+
37
+ // Simulate LOADING state
38
+ scope.value = { type: CoValueLoadingState.LOADING, id };
39
+
40
+ const firstCall = scope.getCurrentValue();
41
+ const secondCall = scope.getCurrentValue();
42
+ const thirdCall = scope.getCurrentValue();
43
+
44
+ // All calls should return the same reference
45
+ expect(firstCall).toBe(secondCall);
46
+ expect(secondCall).toBe(thirdCall);
47
+ expect(firstCall).toBe(thirdCall);
48
+
49
+ // Verify it's a NotLoaded value with LOADING state
50
+ expect(firstCall.$jazz.loadingState).toBe(CoValueLoadingState.LOADING);
51
+ expect(firstCall.$isLoaded).toBe(false);
52
+ expect(firstCall.$jazz.id).toBe(id);
53
+
54
+ // Verify the cached value matches
55
+ expect(scope.unloadedValue).toBe(firstCall);
56
+
57
+ scope.destroy();
58
+ });
59
+
60
+ it("returns different references when the loading state changes", () => {
61
+ const person = Person.create({ name: "John" });
62
+ const node = person.$jazz.raw.core.node;
63
+ const id = person.$jazz.id;
64
+ const schema = {
65
+ ref: coValueClassFromCoValueClassOrSchema(Person),
66
+ optional: false,
67
+ };
68
+
69
+ const scope = new SubscriptionScope(node, true, id, schema);
70
+
71
+ // Start with LOADING state
72
+ scope.value = { type: CoValueLoadingState.LOADING, id };
73
+ const loadingValue = scope.getCurrentValue();
74
+
75
+ // Switch to UNAVAILABLE state
76
+ scope.updateValue(
77
+ new JazzError(id, CoValueLoadingState.UNAVAILABLE, [
78
+ {
79
+ code: CoValueLoadingState.UNAVAILABLE,
80
+ message: "The value is unavailable",
81
+ params: { id },
82
+ path: [],
83
+ },
84
+ ]),
85
+ );
86
+ const unavailableValue = scope.getCurrentValue();
87
+
88
+ // Switch to UNAUTHORIZED state
89
+ scope.updateValue(
90
+ new JazzError(id, CoValueLoadingState.UNAUTHORIZED, [
91
+ {
92
+ code: CoValueLoadingState.UNAUTHORIZED,
93
+ message: "The current user is not authorized to access this value",
94
+ params: { id },
95
+ path: [],
96
+ },
97
+ ]),
98
+ );
99
+ const unauthorizedValue = scope.getCurrentValue();
100
+
101
+ // All should be different references
102
+ expect(loadingValue).not.toBe(unavailableValue);
103
+ expect(loadingValue).not.toBe(unauthorizedValue);
104
+ expect(unavailableValue).not.toBe(unauthorizedValue);
105
+
106
+ // Verify each has the correct loading state
107
+ expect(loadingValue.$jazz.loadingState).toBe(CoValueLoadingState.LOADING);
108
+ expect(unavailableValue.$jazz.loadingState).toBe(
109
+ CoValueLoadingState.UNAVAILABLE,
110
+ );
111
+ expect(unauthorizedValue.$jazz.loadingState).toBe(
112
+ CoValueLoadingState.UNAUTHORIZED,
113
+ );
114
+
115
+ // Verify the cached value is the last one
116
+ expect(scope.unloadedValue).toBe(unauthorizedValue);
117
+
118
+ scope.destroy();
119
+ });
120
+
121
+ it("maintains reference stability across multiple state transitions", () => {
122
+ const person = Person.create({ name: "John" });
123
+ const node = person.$jazz.raw.core.node;
124
+ const id = person.$jazz.id;
125
+ const schema = {
126
+ ref: coValueClassFromCoValueClassOrSchema(Person),
127
+ optional: false,
128
+ };
129
+
130
+ const scope = new SubscriptionScope(node, true, id, schema);
131
+
132
+ // Get LOADING value multiple times
133
+ scope.value = { type: CoValueLoadingState.LOADING, id };
134
+ const loading1 = scope.getCurrentValue();
135
+ const loading2 = scope.getCurrentValue();
136
+ expect(loading1).toBe(loading2);
137
+
138
+ // Switch to UNAVAILABLE
139
+ scope.updateValue(
140
+ new JazzError(id, CoValueLoadingState.UNAVAILABLE, [
141
+ {
142
+ code: CoValueLoadingState.UNAVAILABLE,
143
+ message: "The value is unavailable",
144
+ params: { id },
145
+ path: [],
146
+ },
147
+ ]),
148
+ );
149
+ const unavailable1 = scope.getCurrentValue();
150
+ expect(unavailable1).not.toBe(loading1);
151
+
152
+ // Get UNAVAILABLE again - should return same reference
153
+ const unavailable2 = scope.getCurrentValue();
154
+ expect(unavailable1).toBe(unavailable2);
155
+
156
+ // Switch to UNAUTHORIZED
157
+ scope.updateValue(
158
+ new JazzError(id, CoValueLoadingState.UNAUTHORIZED, [
159
+ {
160
+ code: CoValueLoadingState.UNAUTHORIZED,
161
+ message: "The current user is not authorized to access this value",
162
+ params: { id },
163
+ path: [],
164
+ },
165
+ ]),
166
+ );
167
+ const unauthorized1 = scope.getCurrentValue();
168
+ expect(unauthorized1).not.toBe(unavailable1);
169
+
170
+ // Get UNAUTHORIZED again - should return same reference
171
+ const unauthorized2 = scope.getCurrentValue();
172
+ expect(unauthorized1).toBe(unauthorized2);
173
+
174
+ // Switch back to UNAVAILABLE - should create new reference
175
+ scope.updateValue(
176
+ new JazzError(id, CoValueLoadingState.UNAVAILABLE, [
177
+ {
178
+ code: CoValueLoadingState.UNAVAILABLE,
179
+ message: "The value is unavailable",
180
+ params: { id },
181
+ path: [],
182
+ },
183
+ ]),
184
+ );
185
+ const unavailable3 = scope.getCurrentValue();
186
+ expect(unavailable3).not.toBe(unavailable1);
187
+ expect(unavailable3).not.toBe(unavailable2);
188
+
189
+ // Get UNAVAILABLE again - should return same reference as unavailable3
190
+ const unavailable4 = scope.getCurrentValue();
191
+ expect(unavailable3).toBe(unavailable4);
192
+
193
+ scope.destroy();
194
+ });
195
+
196
+ it("returns stable reference when switching back to a previously used state after cache was overwritten", () => {
197
+ const person = Person.create({ name: "John" });
198
+ const node = person.$jazz.raw.core.node;
199
+ const id = person.$jazz.id;
200
+ const schema = {
201
+ ref: coValueClassFromCoValueClassOrSchema(Person),
202
+ optional: false,
203
+ };
204
+
205
+ const scope = new SubscriptionScope(node, true, id, schema);
206
+
207
+ // First, get a LOADING value
208
+ scope.value = { type: CoValueLoadingState.LOADING, id };
209
+ const firstLoadingValue = scope.getCurrentValue();
210
+
211
+ // Switch to UNAVAILABLE (this overwrites the cache)
212
+ scope.updateValue(
213
+ new JazzError(id, CoValueLoadingState.UNAVAILABLE, [
214
+ {
215
+ code: CoValueLoadingState.UNAVAILABLE,
216
+ message: "The value is unavailable",
217
+ params: { id },
218
+ path: [],
219
+ },
220
+ ]),
221
+ );
222
+ const unavailableValue = scope.getCurrentValue();
223
+
224
+ // Switch back to LOADING (should create a new reference since cache was overwritten)
225
+ scope.value = { type: CoValueLoadingState.LOADING, id };
226
+ const secondLoadingValue = scope.getCurrentValue();
227
+
228
+ // The second LOADING value should be different from the first
229
+ // because the cache was overwritten with UNAVAILABLE
230
+ expect(firstLoadingValue).not.toBe(secondLoadingValue);
231
+
232
+ // But both should have the same loading state
233
+ expect(firstLoadingValue.$jazz.loadingState).toBe(
234
+ CoValueLoadingState.LOADING,
235
+ );
236
+ expect(secondLoadingValue.$jazz.loadingState).toBe(
237
+ CoValueLoadingState.LOADING,
238
+ );
239
+
240
+ // The cache should now point to the second LOADING value
241
+ expect(scope.unloadedValue).toBe(secondLoadingValue);
242
+
243
+ scope.destroy();
244
+ });
245
+
246
+ it("preserves correct loading state in returned value", () => {
247
+ const person = Person.create({ name: "John" });
248
+ const node = person.$jazz.raw.core.node;
249
+ const id = person.$jazz.id;
250
+ const schema = {
251
+ ref: coValueClassFromCoValueClassOrSchema(Person),
252
+ optional: false,
253
+ };
254
+
255
+ const scope = new SubscriptionScope(node, true, id, schema);
256
+
257
+ // Test LOADING state
258
+ scope.value = { type: CoValueLoadingState.LOADING, id };
259
+ const loadingValue = scope.getCurrentValue();
260
+ expect(loadingValue.$jazz.loadingState).toBe(CoValueLoadingState.LOADING);
261
+ expect(loadingValue.$isLoaded).toBe(false);
262
+ expect(loadingValue.$jazz.id).toBe(id);
263
+
264
+ // Test UNAVAILABLE state
265
+ scope.updateValue(
266
+ new JazzError(id, CoValueLoadingState.UNAVAILABLE, [
267
+ {
268
+ code: CoValueLoadingState.UNAVAILABLE,
269
+ message: "The value is unavailable",
270
+ params: { id },
271
+ path: [],
272
+ },
273
+ ]),
274
+ );
275
+ const unavailableValue = scope.getCurrentValue();
276
+ expect(unavailableValue.$jazz.loadingState).toBe(
277
+ CoValueLoadingState.UNAVAILABLE,
278
+ );
279
+ expect(unavailableValue.$isLoaded).toBe(false);
280
+ expect(unavailableValue.$jazz.id).toBe(id);
281
+
282
+ // Test UNAUTHORIZED state
283
+ scope.updateValue(
284
+ new JazzError(id, CoValueLoadingState.UNAUTHORIZED, [
285
+ {
286
+ code: CoValueLoadingState.UNAUTHORIZED,
287
+ message: "The current user is not authorized to access this value",
288
+ params: { id },
289
+ path: [],
290
+ },
291
+ ]),
292
+ );
293
+ const unauthorizedValue = scope.getCurrentValue();
294
+ expect(unauthorizedValue.$jazz.loadingState).toBe(
295
+ CoValueLoadingState.UNAUTHORIZED,
296
+ );
297
+ expect(unauthorizedValue.$isLoaded).toBe(false);
298
+ expect(unauthorizedValue.$jazz.id).toBe(id);
299
+
300
+ scope.destroy();
301
+ });
302
+
303
+ it("returns LOADING state when shouldSendUpdates returns false", () => {
304
+ const person = Person.create({ name: "John" });
305
+ const node = person.$jazz.raw.core.node;
306
+ const id = person.$jazz.id;
307
+ const schema = {
308
+ ref: coValueClassFromCoValueClassOrSchema(Person),
309
+ optional: false,
310
+ };
311
+
312
+ const scope = new SubscriptionScope(node, true, id, schema);
313
+
314
+ // Set up a loaded value with pending children
315
+ const loadedPerson = Person.create({ name: "Jane" });
316
+ scope.updateValue({
317
+ type: CoValueLoadingState.LOADED,
318
+ value: loadedPerson,
319
+ id: loadedPerson.$jazz.id,
320
+ });
321
+
322
+ // Add a pending child to make shouldSendUpdates return false
323
+ scope.pendingLoadedChildren.add("some-child-id");
324
+
325
+ const value1 = scope.getCurrentValue();
326
+ const value2 = scope.getCurrentValue();
327
+
328
+ // Should return the same LOADING reference
329
+ expect(value1).toBe(value2);
330
+ expect(value1.$jazz.loadingState).toBe(CoValueLoadingState.LOADING);
331
+ expect(value1.$isLoaded).toBe(false);
332
+
333
+ // Clear pending children
334
+ scope.pendingLoadedChildren.clear();
335
+
336
+ // Now should return the loaded value
337
+ const loadedValue = scope.getCurrentValue();
338
+ expect(loadedValue).toBe(loadedPerson);
339
+ expect(loadedValue.$isLoaded).toBe(true);
340
+
341
+ scope.destroy();
342
+ });
343
+
344
+ it("returns error state from errorFromChildren when present", () => {
345
+ const person = Person.create({ name: "John" });
346
+ const node = person.$jazz.raw.core.node;
347
+ const id = person.$jazz.id;
348
+ const schema = {
349
+ ref: coValueClassFromCoValueClassOrSchema(Person),
350
+ optional: false,
351
+ };
352
+
353
+ const scope = new SubscriptionScope(node, true, id, schema);
354
+
355
+ // Set up a loaded value
356
+ const loadedPerson = Person.create({ name: "Jane" });
357
+ scope.updateValue({
358
+ type: CoValueLoadingState.LOADED,
359
+ value: loadedPerson,
360
+ id: loadedPerson.$jazz.id,
361
+ });
362
+
363
+ // Set up an error from children
364
+ const childError = new JazzError(
365
+ "child-id" as any,
366
+ CoValueLoadingState.UNAVAILABLE,
367
+ [
368
+ {
369
+ code: CoValueLoadingState.UNAVAILABLE,
370
+ message: "Child value is unavailable",
371
+ params: { id: "child-id" },
372
+ path: [],
373
+ },
374
+ ],
375
+ );
376
+ scope.errorFromChildren = childError;
377
+
378
+ const value1 = scope.getCurrentValue();
379
+ const value2 = scope.getCurrentValue();
380
+
381
+ // Should return the same UNAVAILABLE reference
382
+ expect(value1).toBe(value2);
383
+ expect(value1.$jazz.loadingState).toBe(CoValueLoadingState.UNAVAILABLE);
384
+ expect(value1.$isLoaded).toBe(false);
385
+
386
+ // Clear the error
387
+ scope.errorFromChildren = undefined;
388
+
389
+ // Now should return the loaded value
390
+ const loadedValue = scope.getCurrentValue();
391
+ expect(loadedValue).toBe(loadedPerson);
392
+ expect(loadedValue.$isLoaded).toBe(true);
393
+
394
+ scope.destroy();
395
+ });
396
+ });
397
+ });
@@ -1169,6 +1169,28 @@ test("should not throw when calling ensureLoaded a record with a deleted ref", a
1169
1169
  unsub();
1170
1170
  });
1171
1171
 
1172
+ test("should not throw when calling ensureLoaded a record with a non-existent key if there's a catch block", async () => {
1173
+ const Person = co.record(
1174
+ z.string(),
1175
+ co.map({
1176
+ name: z.string(),
1177
+ breed: z.string(),
1178
+ }),
1179
+ );
1180
+
1181
+ const person = Person.create({});
1182
+
1183
+ const loadedPerson = await person.$jazz.ensureLoaded({
1184
+ resolve: {
1185
+ ["pet1"]: {
1186
+ $onError: "catch",
1187
+ },
1188
+ },
1189
+ });
1190
+
1191
+ expect(loadedPerson.pet1).toBeUndefined();
1192
+ });
1193
+
1172
1194
  // This was a regression that ocurred when we migrated `DeeplyLoaded` to use explicit loading states.
1173
1195
  // Keeping this test to prevent it from happening again.
1174
1196
  test("deep loaded CoList nested inside another CoValue can be iterated over", async () => {
@@ -1296,6 +1296,75 @@ describe("subscribeToCoValue", () => {
1296
1296
  expect(result.data.length).toBe(chunks + 1);
1297
1297
  expect(result.data[chunks]).toBe("new entry");
1298
1298
  });
1299
+
1300
+ it.fails(
1301
+ "should return the latest loaded state when a deeply loaded child becomes not accessible",
1302
+ async () => {
1303
+ const Dog = co.map({
1304
+ name: z.string(),
1305
+ });
1306
+
1307
+ const Person = co.map({
1308
+ name: z.string(),
1309
+ dog: Dog,
1310
+ });
1311
+
1312
+ const reader = await createJazzTestAccount({
1313
+ isCurrentActiveAccount: true,
1314
+ });
1315
+
1316
+ const creator = await createJazzTestAccount({
1317
+ isCurrentActiveAccount: true,
1318
+ });
1319
+
1320
+ const dogGroup = Group.create(creator);
1321
+ dogGroup.addMember(reader, "reader");
1322
+
1323
+ const everyone = Group.create(creator);
1324
+ everyone.addMember("everyone", "reader");
1325
+
1326
+ const dog = Dog.create({ name: "Giggino" }, dogGroup);
1327
+ const person = Person.create({ name: "Guido", dog }, everyone);
1328
+
1329
+ let result = null as Loaded<typeof Person, { dog: true }> | null;
1330
+
1331
+ const updateFn = vi.fn().mockImplementation((value) => {
1332
+ result = value;
1333
+ });
1334
+
1335
+ const unsubscribe = subscribeToCoValue(
1336
+ coValueClassFromCoValueClassOrSchema(Person),
1337
+ person.$jazz.id,
1338
+ {
1339
+ loadAs: reader,
1340
+ resolve: {
1341
+ dog: true,
1342
+ },
1343
+ },
1344
+ updateFn,
1345
+ );
1346
+
1347
+ onTestFinished(unsubscribe);
1348
+
1349
+ await waitFor(() => {
1350
+ assert(result);
1351
+ expect(result.name).toBe("Guido");
1352
+ assertLoaded(result.dog);
1353
+ expect(result.dog.name).toBe("Giggino");
1354
+ });
1355
+
1356
+ // Make the dog not accessible by removing the reader from the dog's group
1357
+ dogGroup.removeMember(reader);
1358
+
1359
+ await new Promise((resolve) => setTimeout(resolve, 10));
1360
+
1361
+ assert(result);
1362
+
1363
+ // The parent & child loading state should be in sync, but because the child loading state
1364
+ // is mutable it becomes not loaded while the parent is still loaded
1365
+ expect(result.$isLoaded).toBe(result.dog.$isLoaded);
1366
+ },
1367
+ );
1299
1368
  });
1300
1369
 
1301
1370
  describe("getSubscriptionScope", () => {