jazz-tools 0.13.30 → 0.13.33

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 (105) hide show
  1. package/.turbo/turbo-build.log +23 -22
  2. package/.turbo/turbo-lint.log +4 -0
  3. package/.turbo/turbo-test.log +144 -0
  4. package/CHANGELOG.md +16 -0
  5. package/dist/auth/DemoAuth.d.ts.map +1 -1
  6. package/dist/auth/PassphraseAuth.d.ts +1 -3
  7. package/dist/auth/PassphraseAuth.d.ts.map +1 -1
  8. package/dist/{chunk-LMV6J7GN.js → chunk-WLOZKDOH.js} +3532 -3269
  9. package/dist/chunk-WLOZKDOH.js.map +1 -0
  10. package/dist/coValues/CoValueBase.d.ts +22 -0
  11. package/dist/coValues/CoValueBase.d.ts.map +1 -0
  12. package/dist/coValues/account.d.ts +12 -12
  13. package/dist/coValues/account.d.ts.map +1 -1
  14. package/dist/coValues/coFeed.d.ts +24 -25
  15. package/dist/coValues/coFeed.d.ts.map +1 -1
  16. package/dist/coValues/coList.d.ts +10 -13
  17. package/dist/coValues/coList.d.ts.map +1 -1
  18. package/dist/coValues/coMap.d.ts +32 -35
  19. package/dist/coValues/coMap.d.ts.map +1 -1
  20. package/dist/coValues/coPlainText.d.ts.map +1 -1
  21. package/dist/coValues/deepLoading.d.ts +28 -22
  22. package/dist/coValues/deepLoading.d.ts.map +1 -1
  23. package/dist/coValues/extensions/imageDef.d.ts +12 -11
  24. package/dist/coValues/extensions/imageDef.d.ts.map +1 -1
  25. package/dist/coValues/group.d.ts +5 -9
  26. package/dist/coValues/group.d.ts.map +1 -1
  27. package/dist/coValues/inbox.d.ts +2 -3
  28. package/dist/coValues/inbox.d.ts.map +1 -1
  29. package/dist/coValues/interfaces.d.ts +8 -34
  30. package/dist/coValues/interfaces.d.ts.map +1 -1
  31. package/dist/coValues/profile.d.ts +4 -14
  32. package/dist/coValues/profile.d.ts.map +1 -1
  33. package/dist/coValues/registeredSchemas.d.ts +1 -3
  34. package/dist/coValues/registeredSchemas.d.ts.map +1 -1
  35. package/dist/coValues/schemaUnion.d.ts +6 -6
  36. package/dist/exports.d.ts +12 -16
  37. package/dist/exports.d.ts.map +1 -1
  38. package/dist/implementation/ContextManager.d.ts +1 -1
  39. package/dist/implementation/ContextManager.d.ts.map +1 -1
  40. package/dist/implementation/activeAccountContext.d.ts +1 -1
  41. package/dist/implementation/activeAccountContext.d.ts.map +1 -1
  42. package/dist/implementation/createContext.d.ts +10 -10
  43. package/dist/implementation/createContext.d.ts.map +1 -1
  44. package/dist/implementation/invites.d.ts +6 -6
  45. package/dist/implementation/invites.d.ts.map +1 -1
  46. package/dist/implementation/refs.d.ts +2 -2
  47. package/dist/implementation/refs.d.ts.map +1 -1
  48. package/dist/implementation/schema.d.ts +21 -28
  49. package/dist/implementation/schema.d.ts.map +1 -1
  50. package/dist/implementation/zodSchema/runtimeConverters/zodFieldToCoFieldDef.d.ts +9 -0
  51. package/dist/implementation/zodSchema/runtimeConverters/zodFieldToCoFieldDef.d.ts.map +1 -0
  52. package/dist/implementation/zodSchema/runtimeConverters/zodSchemaToCoSchema.d.ts +28 -0
  53. package/dist/implementation/zodSchema/runtimeConverters/zodSchemaToCoSchema.d.ts.map +1 -0
  54. package/dist/implementation/zodSchema/schemaTypes/AccountSchema.d.ts +65 -0
  55. package/dist/implementation/zodSchema/schemaTypes/AccountSchema.d.ts.map +1 -0
  56. package/dist/implementation/zodSchema/schemaTypes/CoFeedSchema.d.ts +28 -0
  57. package/dist/implementation/zodSchema/schemaTypes/CoFeedSchema.d.ts.map +1 -0
  58. package/dist/implementation/zodSchema/schemaTypes/CoListSchema.d.ts +24 -0
  59. package/dist/implementation/zodSchema/schemaTypes/CoListSchema.d.ts.map +1 -0
  60. package/dist/implementation/zodSchema/schemaTypes/CoMapSchema.d.ts +44 -0
  61. package/dist/implementation/zodSchema/schemaTypes/CoMapSchema.d.ts.map +1 -0
  62. package/dist/implementation/zodSchema/schemaTypes/CoRecordSchema.d.ts +35 -0
  63. package/dist/implementation/zodSchema/schemaTypes/CoRecordSchema.d.ts.map +1 -0
  64. package/dist/implementation/zodSchema/schemaTypes/FileStreamSchema.d.ts +9 -0
  65. package/dist/implementation/zodSchema/schemaTypes/FileStreamSchema.d.ts.map +1 -0
  66. package/dist/implementation/zodSchema/schemaTypes/PlainTextSchema.d.ts +20 -0
  67. package/dist/implementation/zodSchema/schemaTypes/PlainTextSchema.d.ts.map +1 -0
  68. package/dist/implementation/zodSchema/schemaTypes/RichTextSchema.d.ts +18 -0
  69. package/dist/implementation/zodSchema/schemaTypes/RichTextSchema.d.ts.map +1 -0
  70. package/dist/implementation/zodSchema/typeConverters/InstanceOfSchema.d.ts +24 -0
  71. package/dist/implementation/zodSchema/typeConverters/InstanceOfSchema.d.ts.map +1 -0
  72. package/dist/implementation/zodSchema/typeConverters/InstanceOfSchemaCoValuesNullable.d.ts +21 -0
  73. package/dist/implementation/zodSchema/typeConverters/InstanceOfSchemaCoValuesNullable.d.ts.map +1 -0
  74. package/dist/implementation/zodSchema/typeConverters/InstanceOrPrimitiveOfSchema.d.ts +29 -0
  75. package/dist/implementation/zodSchema/typeConverters/InstanceOrPrimitiveOfSchema.d.ts.map +1 -0
  76. package/dist/implementation/zodSchema/typeConverters/InstanceOrPrimitiveOfSchemaCoValuesNullable.d.ts +29 -0
  77. package/dist/implementation/zodSchema/typeConverters/InstanceOrPrimitiveOfSchemaCoValuesNullable.d.ts.map +1 -0
  78. package/dist/implementation/zodSchema/unionUtils.d.ts +6 -0
  79. package/dist/implementation/zodSchema/unionUtils.d.ts.map +1 -0
  80. package/dist/implementation/zodSchema/zodCo.d.ts +35 -0
  81. package/dist/implementation/zodSchema/zodCo.d.ts.map +1 -0
  82. package/dist/implementation/zodSchema/zodSchema.d.ts +38 -0
  83. package/dist/implementation/zodSchema/zodSchema.d.ts.map +1 -0
  84. package/dist/index.js +295 -10
  85. package/dist/index.js.map +1 -1
  86. package/dist/internal.d.ts +34 -0
  87. package/dist/internal.d.ts.map +1 -1
  88. package/dist/subscribe/SubscriptionScope.d.ts +2 -2
  89. package/dist/subscribe/SubscriptionScope.d.ts.map +1 -1
  90. package/dist/subscribe/utils.d.ts +2 -2
  91. package/dist/subscribe/utils.d.ts.map +1 -1
  92. package/dist/testing.d.ts +10 -8
  93. package/dist/testing.d.ts.map +1 -1
  94. package/dist/testing.js +1 -1
  95. package/dist/testing.js.map +1 -1
  96. package/dist/tests/utils.d.ts +6 -2
  97. package/dist/tests/utils.d.ts.map +1 -1
  98. package/dist/types.d.ts +1 -7
  99. package/dist/types.d.ts.map +1 -1
  100. package/package.json +2 -2
  101. package/src/coValues/deepLoading.ts +49 -31
  102. package/src/subscribe/SubscriptionScope.ts +27 -2
  103. package/src/tests/deepLoading.test-d.ts +393 -0
  104. package/src/tests/deepLoading.test.ts +249 -0
  105. package/dist/chunk-LMV6J7GN.js.map +0 -1
@@ -0,0 +1,393 @@
1
+ import { cojsonInternals } from "cojson";
2
+ import { WasmCrypto } from "cojson/crypto/WasmCrypto";
3
+ import { assert, describe, expect, expectTypeOf, test, vi } from "vitest";
4
+ import {
5
+ Account,
6
+ CoFeed,
7
+ CoList,
8
+ CoMap,
9
+ Group,
10
+ ID,
11
+ Profile,
12
+ SessionID,
13
+ co,
14
+ createJazzContextFromExistingCredentials,
15
+ isControlledAccount,
16
+ } from "../index.js";
17
+ import { randomSessionProvider } from "../internal.js";
18
+ import { createJazzTestAccount } from "../testing.js";
19
+
20
+ const Crypto = await WasmCrypto.create();
21
+ const { connectedPeers } = cojsonInternals;
22
+
23
+ class TestMap extends CoMap {
24
+ list = co.ref(TestList);
25
+ optionalRef = co.ref(InnermostMap, { optional: true });
26
+ }
27
+
28
+ class TestList extends CoList.Of(co.ref(() => InnerMap)) {}
29
+
30
+ class InnerMap extends CoMap {
31
+ stream = co.ref(TestStream);
32
+ }
33
+
34
+ class TestStream extends CoFeed.Of(co.ref(() => InnermostMap)) {}
35
+
36
+ class InnermostMap extends CoMap {
37
+ value = co.string;
38
+ }
39
+
40
+ const me = await createJazzTestAccount({});
41
+ const map = TestMap.create({
42
+ list: TestList.create([], { owner: me }),
43
+ });
44
+
45
+ describe("Deep loading with depth arg", async () => {
46
+ test("load without resolve", async () => {
47
+ const map1 = await TestMap.load(map.id);
48
+
49
+ function validateType(map: TestMap | null) {
50
+ map;
51
+ }
52
+
53
+ validateType(map1);
54
+ });
55
+
56
+ test("load with resolve { list: true }", async () => {
57
+ const map2 = await TestMap.load(map.id, {
58
+ resolve: { list: true },
59
+ });
60
+ expectTypeOf(map2).toEqualTypeOf<
61
+ | (TestMap & {
62
+ list: TestList;
63
+ })
64
+ | null
65
+ >();
66
+ });
67
+
68
+ test("load with resolve { list: { $each: true } }", async () => {
69
+ const map3 = await TestMap.load(map.id, {
70
+ resolve: { list: { $each: true } },
71
+ });
72
+ expectTypeOf(map3).toEqualTypeOf<
73
+ | (TestMap & {
74
+ list: TestList & InnerMap[];
75
+ })
76
+ | null
77
+ >();
78
+ });
79
+
80
+ test("load with resolve { optionalRef: true }", async () => {
81
+ const map3a = await TestMap.load(map.id, {
82
+ resolve: { optionalRef: true } as const,
83
+ });
84
+ expectTypeOf(map3a).toEqualTypeOf<
85
+ | (TestMap & {
86
+ optionalRef: InnermostMap | undefined;
87
+ })
88
+ | null
89
+ >();
90
+ });
91
+
92
+ test("load with resolve { list: { $each: { stream: true } } }", async () => {
93
+ const map4 = await TestMap.load(map.id, {
94
+ resolve: { list: { $each: { stream: true } } },
95
+ });
96
+ expectTypeOf(map4).toEqualTypeOf<
97
+ | (TestMap & {
98
+ list: TestList & (InnerMap & { stream: TestStream })[];
99
+ })
100
+ | null
101
+ >();
102
+ });
103
+
104
+ test("load with resolve { list: { $each: { stream: { $each: true } } } }", async () => {
105
+ const map5 = await TestMap.load(map.id, {
106
+ resolve: { list: { $each: { stream: { $each: true } } } },
107
+ });
108
+ type ExpectedMap5 =
109
+ | (TestMap & {
110
+ list: TestList &
111
+ (InnerMap & {
112
+ stream: TestStream & {
113
+ byMe?: { value: InnermostMap };
114
+ inCurrentSession?: { value: InnermostMap };
115
+ perSession: {
116
+ [sessionID: SessionID]: {
117
+ value: InnermostMap;
118
+ };
119
+ };
120
+ } & {
121
+ [key: ID<Account>]: { value: InnermostMap };
122
+ };
123
+ })[];
124
+ })
125
+ | null;
126
+ expectTypeOf(map5).toEqualTypeOf<ExpectedMap5>();
127
+ });
128
+
129
+ test("should handle $onError on CoList items", async () => {
130
+ class List extends CoList.Of(co.ref(() => InnerMap)) {}
131
+
132
+ const result = await List.load("x" as ID<List>, {
133
+ resolve: { $each: { $onError: null } },
134
+ });
135
+ function validateType(map: ((InnerMap | null)[] & List) | null) {
136
+ map;
137
+ }
138
+
139
+ validateType(result);
140
+ expectTypeOf(result).toEqualTypeOf<((InnerMap | null)[] & List) | null>();
141
+ });
142
+
143
+ test("should handle $onError on a child CoList", async () => {
144
+ class InnerMap extends CoMap {
145
+ value = co.string;
146
+ }
147
+ class List extends CoList.Of(co.ref(InnerMap)) {}
148
+ class MyMap extends CoMap {
149
+ list = co.ref(List);
150
+ }
151
+
152
+ const result = await MyMap.load("x" as ID<MyMap>, {
153
+ resolve: { list: { $each: { $onError: null } } },
154
+ });
155
+
156
+ function validateType(map: (InnerMap | null)[] & List) {
157
+ map;
158
+ }
159
+
160
+ assert(result);
161
+
162
+ validateType(result.list);
163
+ });
164
+
165
+ test("should handle $onError on CoMap.Record", async () => {
166
+ class Record extends CoMap.Record(co.ref(() => InnerMap)) {}
167
+
168
+ const result = await Record.load("x" as ID<Record>, {
169
+ resolve: { $each: { $onError: null } },
170
+ });
171
+
172
+ type ExpectedResult =
173
+ | ({
174
+ [key: string]: InnerMap | null;
175
+ } & Record)
176
+ | null;
177
+
178
+ function validateType(map: ExpectedResult) {
179
+ map;
180
+ }
181
+
182
+ validateType(result);
183
+ expectTypeOf(result).toEqualTypeOf<ExpectedResult>();
184
+ });
185
+
186
+ test("should handle $onError on a child CoMap.Record", async () => {
187
+ class Record extends CoMap.Record(co.ref(() => InnerMap)) {}
188
+ class MyMap extends CoMap {
189
+ record = co.ref(Record);
190
+ }
191
+
192
+ const result = await MyMap.load("x" as ID<MyMap>, {
193
+ resolve: { record: { $each: { $onError: null } } },
194
+ });
195
+
196
+ type ExpectedResult = {
197
+ [key: string]: InnerMap | null;
198
+ } & Record &
199
+ co<Record | null>;
200
+
201
+ function validateType(map: ExpectedResult) {
202
+ map;
203
+ }
204
+
205
+ assert(result);
206
+
207
+ validateType(result.record);
208
+ expectTypeOf(result.record).toEqualTypeOf<ExpectedResult>();
209
+ });
210
+
211
+ test("should handle $onError on a ref", async () => {
212
+ class Person extends CoMap {
213
+ name = co.string;
214
+ dog = co.ref(Dog);
215
+ }
216
+
217
+ class Dog extends CoMap {
218
+ name = co.string;
219
+ }
220
+
221
+ const result = await Person.load("x" as ID<Person>, {
222
+ resolve: { dog: { $onError: null } },
223
+ });
224
+
225
+ type ExpectedResult =
226
+ | (Person & {
227
+ dog: Dog | null;
228
+ })
229
+ | null;
230
+
231
+ function validateType(map: ExpectedResult) {
232
+ map;
233
+ }
234
+
235
+ validateType(result);
236
+ expectTypeOf(result).toEqualTypeOf<ExpectedResult>();
237
+ });
238
+ });
239
+
240
+ class CustomProfile extends Profile {
241
+ stream = co.ref(TestStream);
242
+ }
243
+
244
+ class CustomAccount extends Account {
245
+ profile = co.ref(CustomProfile);
246
+ root = co.ref(TestMap);
247
+
248
+ async migrate(
249
+ this: CustomAccount,
250
+ creationProps?: { name: string } | undefined,
251
+ ) {
252
+ if (creationProps) {
253
+ const profileGroup = Group.create(this);
254
+ this.profile = CustomProfile.create(
255
+ {
256
+ name: creationProps.name,
257
+ stream: TestStream.create([], this),
258
+ },
259
+ profileGroup,
260
+ );
261
+ this.root = TestMap.create({ list: TestList.create([], this) }, this);
262
+ }
263
+
264
+ const thisLoaded = await this.ensureLoaded({
265
+ resolve: {
266
+ profile: { stream: true },
267
+ root: { list: true },
268
+ },
269
+ });
270
+ expectTypeOf(thisLoaded).toEqualTypeOf<
271
+ CustomAccount & {
272
+ profile: CustomProfile & {
273
+ stream: TestStream;
274
+ };
275
+ root: TestMap & {
276
+ list: TestList;
277
+ };
278
+ }
279
+ >();
280
+ }
281
+ }
282
+
283
+ test("Deep loading within account", async () => {
284
+ const me = await CustomAccount.create({
285
+ creationProps: { name: "Hermes Puggington" },
286
+ crypto: Crypto,
287
+ });
288
+
289
+ const meLoaded = await me.ensureLoaded({
290
+ resolve: {
291
+ profile: { stream: true },
292
+ root: { list: true },
293
+ },
294
+ });
295
+ expectTypeOf(meLoaded).toEqualTypeOf<
296
+ CustomAccount & {
297
+ profile: CustomProfile & {
298
+ stream: TestStream;
299
+ };
300
+ root: TestMap & {
301
+ list: TestList;
302
+ };
303
+ }
304
+ >();
305
+ });
306
+
307
+ class RecordLike extends CoMap.Record(co.ref(TestMap)) {}
308
+
309
+ test("Deep loading a record-like coMap", async () => {
310
+ const me = await Account.create({
311
+ creationProps: { name: "Hermes Puggington" },
312
+ crypto: Crypto,
313
+ });
314
+
315
+ const [initialAsPeer, secondPeer] = connectedPeers("initial", "second", {
316
+ peer1role: "server",
317
+ peer2role: "client",
318
+ });
319
+
320
+ if (!isControlledAccount(me)) {
321
+ throw "me is not a controlled account";
322
+ }
323
+
324
+ me._raw.core.node.syncManager.addPeer(secondPeer);
325
+ const { account: meOnSecondPeer } =
326
+ await createJazzContextFromExistingCredentials({
327
+ credentials: {
328
+ accountID: me.id,
329
+ secret: me._raw.core.node.agentSecret,
330
+ },
331
+ sessionProvider: randomSessionProvider,
332
+ peersToLoadFrom: [initialAsPeer],
333
+ crypto: Crypto,
334
+ });
335
+
336
+ const record = RecordLike.create(
337
+ {
338
+ key1: TestMap.create(
339
+ { list: TestList.create([], { owner: me }) },
340
+ { owner: me },
341
+ ),
342
+ key2: TestMap.create(
343
+ { list: TestList.create([], { owner: me }) },
344
+ { owner: me },
345
+ ),
346
+ },
347
+ { owner: me },
348
+ );
349
+
350
+ const recordLoaded = await RecordLike.load(record.id, {
351
+ loadAs: meOnSecondPeer,
352
+ resolve: {
353
+ $each: { list: { $each: true } },
354
+ },
355
+ });
356
+ expectTypeOf(recordLoaded).toEqualTypeOf<
357
+ | (RecordLike & {
358
+ [key: string]: TestMap & {
359
+ list: TestList & InnerMap[];
360
+ };
361
+ })
362
+ | null
363
+ >();
364
+ });
365
+
366
+ test("The resolve type doesn't accept extra keys", async () => {
367
+ const me = await CustomAccount.create({
368
+ creationProps: { name: "Hermes Puggington" },
369
+ crypto: Crypto,
370
+ });
371
+
372
+ const meLoaded = await me.ensureLoaded({
373
+ resolve: {
374
+ // @ts-expect-error
375
+ profile: { stream: true, extraKey: true },
376
+ // @ts-expect-error
377
+ root: { list: true, extraKey: true },
378
+ },
379
+ });
380
+
381
+ expectTypeOf(meLoaded).toEqualTypeOf<
382
+ CustomAccount & {
383
+ profile: CustomProfile & {
384
+ stream: TestStream;
385
+ extraKey: never;
386
+ };
387
+ root: TestMap & {
388
+ list: TestList;
389
+ extraKey: never;
390
+ };
391
+ }
392
+ >();
393
+ });
@@ -612,6 +612,254 @@ describe("Deep loading with unauthorized account", async () => {
612
612
 
613
613
  expect(loadedMap?.id).toBe(map.id);
614
614
  });
615
+
616
+ test("unaccessible record element with $onError", async () => {
617
+ class Person extends CoMap {
618
+ name = co.string;
619
+ }
620
+ class Friends extends CoMap.Record(co.ref(Person)) {}
621
+
622
+ const map = Friends.create(
623
+ {
624
+ jane: Person.create({ name: "Jane" }, onlyBob),
625
+ alice: Person.create({ name: "Alice" }, group),
626
+ },
627
+ group,
628
+ );
629
+
630
+ const friendsOnAlice = await Friends.load(map.id, {
631
+ resolve: { $each: { $onError: null } },
632
+ loadAs: alice,
633
+ });
634
+
635
+ assert(friendsOnAlice, "friendsOnAlice is null");
636
+
637
+ expect(friendsOnAlice.jane).toBeNull();
638
+ expect(friendsOnAlice.alice).not.toBeNull();
639
+ });
640
+
641
+ test("unaccessible nested record element with $onError", async () => {
642
+ class Person extends CoMap {
643
+ name = co.string;
644
+ }
645
+ class Friends extends CoMap.Record(co.ref(Person)) {}
646
+
647
+ class User extends CoMap {
648
+ name = co.string;
649
+ friends = co.ref(Friends);
650
+ }
651
+
652
+ const map = User.create(
653
+ {
654
+ name: "John",
655
+ friends: Friends.create(
656
+ {
657
+ jane: Person.create({ name: "Jane" }, onlyBob),
658
+ alice: Person.create({ name: "Alice" }, group),
659
+ },
660
+ group,
661
+ ),
662
+ },
663
+ group,
664
+ );
665
+
666
+ const user = await User.load(map.id, {
667
+ resolve: { friends: { $each: { $onError: null } } },
668
+ loadAs: alice,
669
+ });
670
+
671
+ assert(user, "user is null");
672
+
673
+ expect(user.friends.jane).toBeNull();
674
+ expect(user.friends.alice).not.toBeNull();
675
+ });
676
+
677
+ test("unaccessible element down the chain with $onError on a record", async () => {
678
+ class Person extends CoMap {
679
+ name = co.string;
680
+ dog = co.ref(Dog);
681
+ }
682
+ class Dog extends CoMap {
683
+ name = co.string;
684
+ }
685
+ class Friends extends CoMap.Record(co.ref(Person)) {}
686
+
687
+ class User extends CoMap {
688
+ name = co.string;
689
+ friends = co.ref(Friends);
690
+ }
691
+
692
+ const map = User.create(
693
+ {
694
+ name: "John",
695
+ friends: Friends.create(
696
+ {
697
+ jane: Person.create(
698
+ {
699
+ name: "Jane",
700
+ dog: Dog.create({ name: "Rex" }, onlyBob), // Jane dog is inaccessible
701
+ },
702
+ group,
703
+ ),
704
+ alice: Person.create(
705
+ { name: "Alice", dog: Dog.create({ name: "Giggino" }, group) },
706
+ group,
707
+ ),
708
+ },
709
+ group,
710
+ ),
711
+ },
712
+ group,
713
+ );
714
+
715
+ const user = await User.load(map.id, {
716
+ resolve: { friends: { $each: { dog: true, $onError: null } } },
717
+ loadAs: alice,
718
+ });
719
+
720
+ assert(user);
721
+
722
+ expect(user.friends.jane).toBeNull(); // jane is null because her dog is inaccessible
723
+ expect(user.friends.alice?.dog).not.toBeNull(); // alice is not null because we have read access to her and her dog
724
+ });
725
+
726
+ test("unaccessible list element with $onError and $each with depth", async () => {
727
+ class Person extends CoMap {
728
+ name = co.string;
729
+ friends = co.optional.ref(Friends);
730
+ }
731
+ class Friends extends CoList.Of(co.ref(Person)) {}
732
+
733
+ const list = Friends.create(
734
+ [
735
+ Person.create(
736
+ {
737
+ name: "Jane",
738
+ friends: Friends.create(
739
+ [Person.create({ name: "Bob" }, onlyBob)],
740
+ group,
741
+ ),
742
+ },
743
+ group,
744
+ ),
745
+ Person.create(
746
+ {
747
+ name: "Alice",
748
+ friends: Friends.create(
749
+ [Person.create({ name: "Bob" }, group)],
750
+ group,
751
+ ),
752
+ },
753
+ group,
754
+ ),
755
+ ],
756
+ group,
757
+ );
758
+
759
+ // The error List -> Jane -> Bob should be propagated to the list element Jane
760
+ // and we should have [null, Alice]
761
+ const listOnAlice = await Friends.load(list.id, {
762
+ resolve: { $each: { friends: { $each: true }, $onError: null } },
763
+ loadAs: alice,
764
+ });
765
+
766
+ assert(listOnAlice, "listOnAlice is null");
767
+
768
+ expect(listOnAlice[0]).toBeNull();
769
+ expect(listOnAlice[1]).not.toBeNull();
770
+ expect(listOnAlice[1]?.name).toBe("Alice");
771
+ expect(listOnAlice[1]?.friends).not.toBeNull();
772
+ expect(listOnAlice[1]?.friends?.[0]?.name).toBe("Bob");
773
+ expect(listOnAlice).toHaveLength(2);
774
+ });
775
+
776
+ test("unaccessible record element with $onError", async () => {
777
+ class Person extends CoMap {
778
+ name = co.string;
779
+ }
780
+ class Friend extends CoMap.Record(co.ref(Person)) {}
781
+
782
+ const map = Friend.create(
783
+ {
784
+ jane: Person.create({ name: "Jane" }, onlyBob),
785
+ alice: Person.create({ name: "Alice" }, group),
786
+ },
787
+ group,
788
+ );
789
+
790
+ const friendsOnAlice = await Friend.load(map.id, {
791
+ resolve: { $each: { $onError: null } },
792
+ loadAs: alice,
793
+ });
794
+
795
+ assert(friendsOnAlice, "friendsOnAlice is null");
796
+
797
+ expect(friendsOnAlice.jane).toBeNull();
798
+ expect(friendsOnAlice.alice).not.toBeNull();
799
+ });
800
+
801
+ test("unaccessible ref catched with $onError", async () => {
802
+ class Person extends CoMap {
803
+ name = co.string;
804
+ dog = co.ref(Dog);
805
+ }
806
+ class Dog extends CoMap {
807
+ name = co.string;
808
+ }
809
+ class Friends extends CoMap.Record(co.ref(Person)) {}
810
+
811
+ class User extends CoMap {
812
+ name = co.string;
813
+ friends = co.ref(Friends);
814
+ }
815
+
816
+ const map = User.create(
817
+ {
818
+ name: "John",
819
+ friends: Friends.create(
820
+ {
821
+ jane: Person.create(
822
+ {
823
+ name: "Jane",
824
+ dog: Dog.create({ name: "Rex" }, onlyBob), // Jane dog is inaccessible
825
+ },
826
+ group,
827
+ ),
828
+ alice: Person.create(
829
+ { name: "Alice", dog: Dog.create({ name: "Giggino" }, group) },
830
+ group,
831
+ ),
832
+ },
833
+ group,
834
+ ),
835
+ },
836
+ group,
837
+ );
838
+
839
+ const user = await User.load(map.id, {
840
+ resolve: { friends: { $each: { dog: { $onError: null } } } },
841
+ loadAs: alice,
842
+ });
843
+
844
+ assert(user);
845
+
846
+ expect(user.friends.jane?.dog).toBeNull(); // jane is null because her dog is inaccessible
847
+ expect(user.friends.alice?.dog?.name).toBe("Giggino"); // alice is not null because we have read access to her and her dog
848
+ });
849
+
850
+ test("using $onError on the resolve root", async () => {
851
+ class Person extends CoMap {
852
+ name = co.string;
853
+ }
854
+
855
+ const map = Person.create({ name: "John" }, onlyBob);
856
+ const user = await Person.load(map.id, {
857
+ resolve: { $onError: null },
858
+ loadAs: alice,
859
+ });
860
+
861
+ expect(user).toBeNull();
862
+ });
615
863
  });
616
864
 
617
865
  test("doesn't break on Map.Record key deletion when the key is referenced in the depth", async () => {
@@ -689,6 +937,7 @@ test("throw when calling ensureLoaded on a ref that is not defined in the schema
689
937
 
690
938
  await expect(
691
939
  root.ensureLoaded({
940
+ // @ts-expect-error missing required ref
692
941
  resolve: { profile: true },
693
942
  }),
694
943
  ).rejects.toThrow("Failed to deeply load CoValue " + root.id);