jazz-tools 0.7.0-alpha.8 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. package/.eslintrc.cjs +3 -10
  2. package/.prettierrc.js +9 -0
  3. package/.turbo/turbo-build.log +3 -19
  4. package/.turbo/turbo-lint.log +4 -0
  5. package/.turbo/turbo-test.log +140 -0
  6. package/CHANGELOG.md +304 -0
  7. package/README.md +10 -2
  8. package/dist/coValues/account.js +59 -41
  9. package/dist/coValues/account.js.map +1 -1
  10. package/dist/coValues/coList.js +49 -46
  11. package/dist/coValues/coList.js.map +1 -1
  12. package/dist/coValues/coMap.js +143 -44
  13. package/dist/coValues/coMap.js.map +1 -1
  14. package/dist/coValues/coStream.js +144 -35
  15. package/dist/coValues/coStream.js.map +1 -1
  16. package/dist/coValues/deepLoading.js +60 -0
  17. package/dist/coValues/deepLoading.js.map +1 -0
  18. package/dist/coValues/extensions/imageDef.js +10 -7
  19. package/dist/coValues/extensions/imageDef.js.map +1 -1
  20. package/dist/coValues/group.js +49 -13
  21. package/dist/coValues/group.js.map +1 -1
  22. package/dist/coValues/interfaces.js +70 -31
  23. package/dist/coValues/interfaces.js.map +1 -1
  24. package/dist/implementation/devtoolsFormatters.js +114 -0
  25. package/dist/implementation/devtoolsFormatters.js.map +1 -0
  26. package/dist/implementation/refs.js +58 -18
  27. package/dist/implementation/refs.js.map +1 -1
  28. package/dist/implementation/schema.js +58 -0
  29. package/dist/implementation/schema.js.map +1 -0
  30. package/dist/implementation/subscriptionScope.js +19 -1
  31. package/dist/implementation/subscriptionScope.js.map +1 -1
  32. package/dist/implementation/symbols.js +5 -0
  33. package/dist/implementation/symbols.js.map +1 -0
  34. package/dist/index.js +3 -5
  35. package/dist/index.js.map +1 -1
  36. package/dist/internal.js +5 -2
  37. package/dist/internal.js.map +1 -1
  38. package/dist/tests/coList.test.js +51 -48
  39. package/dist/tests/coList.test.js.map +1 -1
  40. package/dist/tests/coMap.test.js +131 -73
  41. package/dist/tests/coMap.test.js.map +1 -1
  42. package/dist/tests/coStream.test.js +56 -41
  43. package/dist/tests/coStream.test.js.map +1 -1
  44. package/dist/tests/deepLoading.test.js +188 -0
  45. package/dist/tests/deepLoading.test.js.map +1 -0
  46. package/dist/tests/groupsAndAccounts.test.js +83 -0
  47. package/dist/tests/groupsAndAccounts.test.js.map +1 -0
  48. package/package.json +17 -9
  49. package/src/coValues/account.ts +113 -125
  50. package/src/coValues/coList.ts +87 -103
  51. package/src/coValues/coMap.ts +200 -147
  52. package/src/coValues/coStream.ts +264 -80
  53. package/src/coValues/deepLoading.ts +229 -0
  54. package/src/coValues/extensions/imageDef.ts +17 -13
  55. package/src/coValues/group.ts +92 -58
  56. package/src/coValues/interfaces.ts +215 -115
  57. package/src/implementation/devtoolsFormatters.ts +110 -0
  58. package/src/implementation/inspect.ts +1 -1
  59. package/src/implementation/refs.ts +80 -28
  60. package/src/implementation/schema.ts +138 -0
  61. package/src/implementation/subscriptionScope.ts +48 -12
  62. package/src/implementation/symbols.ts +11 -0
  63. package/src/index.ts +12 -8
  64. package/src/internal.ts +7 -3
  65. package/src/tests/coList.test.ts +77 -62
  66. package/src/tests/coMap.test.ts +201 -113
  67. package/src/tests/coStream.test.ts +113 -84
  68. package/src/tests/deepLoading.test.ts +301 -0
  69. package/src/tests/groupsAndAccounts.test.ts +91 -0
  70. package/dist/implementation/encoding.js +0 -26
  71. package/dist/implementation/encoding.js.map +0 -1
  72. package/src/implementation/encoding.ts +0 -105
@@ -1,48 +1,46 @@
1
- import { expect, describe, test, beforeEach } from "vitest";
2
-
3
- import { webcrypto } from "node:crypto";
1
+ import { expect, describe, test } from "vitest";
4
2
  import { connectedPeers } from "cojson/src/streamUtils.js";
5
3
  import { newRandomSessionID } from "cojson/src/coValueCore.js";
6
4
  import { Effect, Queue } from "effect";
7
- import { Account, jazzReady, Encoders, CoMap } from "..";
8
- import { val } from "../internal";
9
-
10
- if (!("crypto" in globalThis)) {
11
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
12
- (globalThis as any).crypto = webcrypto;
13
- }
5
+ import {
6
+ Account,
7
+ Encoders,
8
+ CoMap,
9
+ co,
10
+ WasmCrypto,
11
+ isControlledAccount,
12
+ } from "../index.js";
14
13
 
15
- beforeEach(async () => {
16
- await jazzReady;
17
- });
14
+ const Crypto = await WasmCrypto.create();
18
15
 
19
16
  describe("Simple CoMap operations", async () => {
20
17
  const me = await Account.create({
21
- name: "Hermes Puggington",
18
+ creationProps: { name: "Hermes Puggington" },
19
+ crypto: Crypto,
22
20
  });
23
21
 
24
- class TestMap extends CoMap<TestMap> {
25
- color = val.string;
26
- _height = val.number;
27
- birthday = val.encoded(Encoders.Date);
28
- name? = val.string;
22
+ class TestMap extends CoMap {
23
+ color = co.string;
24
+ _height = co.number;
25
+ birthday = co.encoded(Encoders.Date);
26
+ name? = co.string;
29
27
 
30
28
  get roughColor() {
31
29
  return this.color + "ish";
32
30
  }
33
31
  }
34
32
 
35
- console.log("TestMap schema", TestMap.prototype._encoding);
33
+ console.log("TestMap schema", TestMap.prototype._schema);
36
34
 
37
35
  const birthday = new Date();
38
36
 
39
- const map = new TestMap(
37
+ const map = TestMap.create(
40
38
  {
41
39
  color: "red",
42
40
  _height: 10,
43
41
  birthday: birthday,
44
42
  },
45
- { owner: me }
43
+ { owner: me },
46
44
  );
47
45
 
48
46
  test("Construction", () => {
@@ -51,16 +49,11 @@ describe("Simple CoMap operations", async () => {
51
49
  expect(map._height).toEqual(10);
52
50
  expect(map.birthday).toEqual(birthday);
53
51
  expect(map._raw.get("birthday")).toEqual(birthday.toISOString());
54
- expect(Object.keys(map)).toEqual([
55
- "color",
56
- "_height",
57
- "birthday",
58
- "name",
59
- ]);
52
+ expect(Object.keys(map)).toEqual(["color", "_height", "birthday"]);
60
53
  });
61
54
 
62
55
  describe("Mutation", () => {
63
- test("assignment", () => {
56
+ test("assignment & deletion", () => {
64
57
  map.color = "blue";
65
58
  expect(map.color).toEqual("blue");
66
59
  expect(map._raw.get("color")).toEqual("blue");
@@ -79,31 +72,35 @@ describe("Simple CoMap operations", async () => {
79
72
  expect(map.name).toEqual("Secret name");
80
73
  map.name = undefined;
81
74
  expect(map.name).toEqual(undefined);
75
+ expect(Object.keys(map)).toContain("name");
76
+ delete map.name;
77
+ expect(map.name).toEqual(undefined);
78
+ expect(Object.keys(map)).not.toContain("name");
82
79
  });
83
80
  });
84
81
 
85
- class RecursiveMap extends CoMap<RecursiveMap> {
86
- name = val.string;
87
- next: val<RecursiveMap | null> = val.ref(() => RecursiveMap);
82
+ class RecursiveMap extends CoMap {
83
+ name = co.string;
84
+ next?: co<RecursiveMap | null> = co.ref(RecursiveMap);
88
85
  }
89
86
 
90
- const recursiveMap = new RecursiveMap(
87
+ const recursiveMap = RecursiveMap.create(
91
88
  {
92
89
  name: "first",
93
- next: new RecursiveMap(
90
+ next: RecursiveMap.create(
94
91
  {
95
92
  name: "second",
96
- next: new RecursiveMap(
93
+ next: RecursiveMap.create(
97
94
  {
98
95
  name: "third",
99
96
  },
100
- { owner: me }
97
+ { owner: me },
101
98
  ),
102
99
  },
103
- { owner: me }
100
+ { owner: me },
104
101
  ),
105
102
  },
106
- { owner: me }
103
+ { owner: me },
107
104
  );
108
105
 
109
106
  describe("Recursive CoMap", () => {
@@ -114,35 +111,35 @@ describe("Simple CoMap operations", async () => {
114
111
  });
115
112
  });
116
113
 
117
- class MapWithEnumOfMaps extends CoMap<MapWithEnumOfMaps> {
118
- name = val.string;
119
- child = val.ref<typeof ChildA | typeof ChildB>((raw) =>
120
- raw.get("type") === "a" ? ChildA : ChildB
114
+ class MapWithEnumOfMaps extends CoMap {
115
+ name = co.string;
116
+ child = co.ref<typeof ChildA | typeof ChildB>((raw) =>
117
+ raw.get("type") === "a" ? ChildA : ChildB,
121
118
  );
122
119
  }
123
120
 
124
- class ChildA extends CoMap<ChildA> {
125
- type = val.literal("a");
126
- value = val.number;
121
+ class ChildA extends CoMap {
122
+ type = co.literal("a");
123
+ value = co.number;
127
124
  }
128
125
 
129
- class ChildB extends CoMap<ChildB> {
130
- type = val.literal("b");
131
- value = val.string;
126
+ class ChildB extends CoMap {
127
+ type = co.literal("b");
128
+ value = co.string;
132
129
  }
133
130
 
134
- const mapWithEnum = new MapWithEnumOfMaps(
131
+ const mapWithEnum = MapWithEnumOfMaps.create(
135
132
  {
136
133
  name: "enum",
137
- child: new ChildA(
134
+ child: ChildA.create(
138
135
  {
139
136
  type: "a",
140
137
  value: 5,
141
138
  },
142
- { owner: me }
139
+ { owner: me },
143
140
  ),
144
141
  },
145
- { owner: me }
142
+ { owner: me },
146
143
  );
147
144
 
148
145
  test("Enum of maps", () => {
@@ -152,35 +149,45 @@ describe("Simple CoMap operations", async () => {
152
149
  expect(mapWithEnum.child?.id).toBeDefined();
153
150
  });
154
151
 
155
- class SuperClassMap extends CoMap<SuperClassMap> {
156
- name = val.string;
152
+ class SuperClassMap extends CoMap {
153
+ name = co.string;
157
154
  }
158
155
 
156
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
159
157
  class SubClassMap extends SuperClassMap {
160
- name = val.literal("specificString")
161
- value = val.number;
158
+ name = co.literal("specificString");
159
+ value = co.number;
160
+ extra = co.ref(TestMap);
161
+ }
162
+
163
+ class GenericMapWithLoose<out T extends string = string> extends CoMap {
164
+ name = co.json<T>();
162
165
  }
163
- interface SubClassMap extends CoMap<SubClassMap> {}
166
+
167
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
168
+ const loose: GenericMapWithLoose<string> = {} as GenericMapWithLoose<
169
+ "a" | "b"
170
+ >;
164
171
  });
165
172
 
166
173
  describe("CoMap resolution", async () => {
167
- class TwiceNestedMap extends CoMap<TwiceNestedMap> {
168
- taste = val.string;
174
+ class TwiceNestedMap extends CoMap {
175
+ taste = co.string;
169
176
  }
170
177
 
171
- class NestedMap extends CoMap<NestedMap> {
172
- name = val.string;
173
- twiceNested = val.ref(() => TwiceNestedMap);
178
+ class NestedMap extends CoMap {
179
+ name = co.string;
180
+ twiceNested = co.ref(TwiceNestedMap);
174
181
 
175
182
  get _fancyName() {
176
183
  return "Sir " + this.name;
177
184
  }
178
185
  }
179
186
 
180
- class TestMap extends CoMap<TestMap> {
181
- color = val.string;
182
- height = val.number;
183
- nested = val.ref(() => NestedMap);
187
+ class TestMap extends CoMap {
188
+ color = co.string;
189
+ height = co.number;
190
+ nested = co.ref(NestedMap);
184
191
 
185
192
  get _roughColor() {
186
193
  return this.color + "ish";
@@ -189,25 +196,26 @@ describe("CoMap resolution", async () => {
189
196
 
190
197
  const initNodeAndMap = async () => {
191
198
  const me = await Account.create({
192
- name: "Hermes Puggington",
199
+ creationProps: { name: "Hermes Puggington" },
200
+ crypto: Crypto,
193
201
  });
194
202
 
195
- const map = new TestMap(
203
+ const map = TestMap.create(
196
204
  {
197
205
  color: "red",
198
206
  height: 10,
199
- nested: new NestedMap(
207
+ nested: NestedMap.create(
200
208
  {
201
209
  name: "nested",
202
- twiceNested: new TwiceNestedMap(
210
+ twiceNested: TwiceNestedMap.create(
203
211
  { taste: "sour" },
204
- { owner: me }
212
+ { owner: me },
205
213
  ),
206
214
  },
207
- { owner: me }
215
+ { owner: me },
208
216
  ),
209
217
  },
210
- { owner: me }
218
+ { owner: me },
211
219
  );
212
220
 
213
221
  return { me, map };
@@ -232,17 +240,22 @@ describe("CoMap resolution", async () => {
232
240
  const [initialAsPeer, secondPeer] = connectedPeers(
233
241
  "initial",
234
242
  "second",
235
- { peer1role: "server", peer2role: "client" }
243
+ { peer1role: "server", peer2role: "client" },
236
244
  );
245
+ if (!isControlledAccount(me)) {
246
+ throw "me is not a controlled account";
247
+ }
237
248
  me._raw.core.node.syncManager.addPeer(secondPeer);
238
249
  const meOnSecondPeer = await Account.become({
239
250
  accountID: me.id,
240
251
  accountSecret: me._raw.agentSecret,
241
252
  peersToLoadFrom: [initialAsPeer],
253
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
242
254
  sessionID: newRandomSessionID(me.id as any),
255
+ crypto: Crypto,
243
256
  });
244
257
 
245
- const loadedMap = await TestMap.load(map.id, { as: meOnSecondPeer });
258
+ const loadedMap = await TestMap.load(map.id, meOnSecondPeer, {});
246
259
 
247
260
  expect(loadedMap?.color).toEqual("red");
248
261
  expect(loadedMap?.height).toEqual(10);
@@ -250,9 +263,11 @@ describe("CoMap resolution", async () => {
250
263
  expect(loadedMap?._refs.nested?.id).toEqual(map.nested?.id);
251
264
  expect(loadedMap?._refs.nested?.value).toEqual(null);
252
265
 
253
- const loadedNestedMap = await NestedMap.load(map.nested!.id, {
254
- as: meOnSecondPeer,
255
- });
266
+ const loadedNestedMap = await NestedMap.load(
267
+ map.nested!.id,
268
+ meOnSecondPeer,
269
+ {},
270
+ );
256
271
 
257
272
  expect(loadedMap?.nested?.name).toEqual("nested");
258
273
  expect(loadedMap?.nested?._fancyName).toEqual("Sir nested");
@@ -261,23 +276,24 @@ describe("CoMap resolution", async () => {
261
276
 
262
277
  const loadedTwiceNestedMap = await TwiceNestedMap.load(
263
278
  map.nested!.twiceNested!.id,
264
- { as: meOnSecondPeer }
279
+ meOnSecondPeer,
280
+ {},
265
281
  );
266
282
 
267
283
  expect(loadedMap?.nested?.twiceNested?.taste).toEqual("sour");
268
284
  expect(loadedMap?.nested?._refs.twiceNested?.value).toEqual(
269
- loadedTwiceNestedMap
285
+ loadedTwiceNestedMap,
270
286
  );
271
287
 
272
- const otherNestedMap = new NestedMap(
288
+ const otherNestedMap = NestedMap.create(
273
289
  {
274
290
  name: "otherNested",
275
- twiceNested: new TwiceNestedMap(
291
+ twiceNested: TwiceNestedMap.create(
276
292
  { taste: "sweet" },
277
- { owner: meOnSecondPeer }
293
+ { owner: meOnSecondPeer },
278
294
  ),
279
295
  },
280
- { owner: meOnSecondPeer }
296
+ { owner: meOnSecondPeer },
281
297
  );
282
298
 
283
299
  loadedMap!.nested = otherNestedMap;
@@ -294,16 +310,19 @@ describe("CoMap resolution", async () => {
294
310
  const [initialAsPeer, secondAsPeer] = connectedPeers(
295
311
  "initial",
296
312
  "second",
297
- { peer1role: "server", peer2role: "client" }
313
+ { peer1role: "server", peer2role: "client" },
298
314
  );
299
-
315
+ if (!isControlledAccount(me)) {
316
+ throw "me is not a controlled account";
317
+ }
300
318
  me._raw.core.node.syncManager.addPeer(secondAsPeer);
301
-
302
319
  const meOnSecondPeer = await Account.become({
303
320
  accountID: me.id,
304
321
  accountSecret: me._raw.agentSecret,
305
322
  peersToLoadFrom: [initialAsPeer],
323
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
306
324
  sessionID: newRandomSessionID(me.id as any),
325
+ crypto: Crypto,
307
326
  });
308
327
 
309
328
  await Effect.runPromise(
@@ -312,14 +331,17 @@ describe("CoMap resolution", async () => {
312
331
 
313
332
  TestMap.subscribe(
314
333
  map.id,
315
- { as: meOnSecondPeer },
334
+ meOnSecondPeer,
335
+ {},
316
336
  (subscribedMap) => {
317
337
  console.log(
318
338
  "subscribedMap.nested?.twiceNested?.taste",
319
- subscribedMap.nested?.twiceNested?.taste
339
+ subscribedMap.nested?.twiceNested?.taste,
340
+ );
341
+ void Effect.runPromise(
342
+ Queue.offer(queue, subscribedMap),
320
343
  );
321
- Effect.runPromise(Queue.offer(queue, subscribedMap));
322
- }
344
+ },
323
345
  );
324
346
 
325
347
  const update1 = yield* $(Queue.take(queue));
@@ -338,19 +360,19 @@ describe("CoMap resolution", async () => {
338
360
  expect(oldTwiceNested?.taste).toEqual("sour");
339
361
 
340
362
  // When assigning a new nested value, we get an update
341
- const newTwiceNested = new TwiceNestedMap(
363
+ const newTwiceNested = TwiceNestedMap.create(
342
364
  {
343
365
  taste: "sweet",
344
366
  },
345
- { owner: meOnSecondPeer }
367
+ { owner: meOnSecondPeer },
346
368
  );
347
369
 
348
- const newNested = new NestedMap(
370
+ const newNested = NestedMap.create(
349
371
  {
350
372
  name: "newNested",
351
373
  twiceNested: newTwiceNested,
352
374
  },
353
- { owner: meOnSecondPeer }
375
+ { owner: meOnSecondPeer },
354
376
  );
355
377
 
356
378
  update3.nested = newNested;
@@ -370,45 +392,46 @@ describe("CoMap resolution", async () => {
370
392
  newTwiceNested.taste = "umami";
371
393
  const update6 = yield* $(Queue.take(queue));
372
394
  expect(update6.nested?.twiceNested?.taste).toEqual("umami");
373
- })
395
+ }),
374
396
  );
375
397
  });
376
398
 
377
- class TestMapWithOptionalRef extends CoMap<TestMapWithOptionalRef> {
378
- color = val.string;
379
- nested? = val.ref(() => NestedMap);
399
+ class TestMapWithOptionalRef extends CoMap {
400
+ color = co.string;
401
+ nested = co.ref(NestedMap, { optional: true });
380
402
  }
381
403
 
382
404
  test("Construction with optional", async () => {
383
405
  const me = await Account.create({
384
- name: "Hermes Puggington",
406
+ creationProps: { name: "Hermes Puggington" },
407
+ crypto: Crypto,
385
408
  });
386
409
 
387
- const mapWithout = new TestMapWithOptionalRef(
410
+ const mapWithout = TestMapWithOptionalRef.create(
388
411
  {
389
412
  color: "red",
390
413
  },
391
- { owner: me }
414
+ { owner: me },
392
415
  );
393
416
 
394
417
  expect(mapWithout.color).toEqual("red");
395
418
  expect(mapWithout.nested).toEqual(undefined);
396
419
 
397
- const mapWith = new TestMapWithOptionalRef(
420
+ const mapWith = TestMapWithOptionalRef.create(
398
421
  {
399
422
  color: "red",
400
- nested: new NestedMap(
423
+ nested: NestedMap.create(
401
424
  {
402
425
  name: "wow!",
403
- twiceNested: new TwiceNestedMap(
426
+ twiceNested: TwiceNestedMap.create(
404
427
  { taste: "sour" },
405
- { owner: me }
428
+ { owner: me },
406
429
  ),
407
430
  },
408
- { owner: me }
431
+ { owner: me },
409
432
  ),
410
433
  },
411
- { owner: me }
434
+ { owner: me },
412
435
  );
413
436
 
414
437
  expect(mapWith.color).toEqual("red");
@@ -417,22 +440,54 @@ describe("CoMap resolution", async () => {
417
440
  expect(mapWith.nested?._raw).toBeDefined();
418
441
  });
419
442
 
420
- class TestRecord extends CoMap<TestRecord> {
421
- [val.items] = val.number;
443
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
444
+ class TestRecord extends CoMap {
445
+ [co.items] = co.number;
422
446
  }
447
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
423
448
  interface TestRecord extends Record<string, number> {}
424
449
 
425
450
  test("Construction with index signature", async () => {
426
451
  const me = await Account.create({
427
- name: "Hermes Puggington",
452
+ creationProps: { name: "Hermes Puggington" },
453
+ crypto: Crypto,
454
+ });
455
+
456
+ const record = TestRecord.create(
457
+ {
458
+ height: 5,
459
+ other: 3,
460
+ },
461
+ { owner: me },
462
+ );
463
+
464
+ expect(record.height).toEqual(5);
465
+ expect(record._raw.get("height")).toEqual(5);
466
+ expect(record.other).toEqual(3);
467
+ expect(record._raw.get("other")).toEqual(3);
468
+ expect(Object.keys(record)).toEqual(["height", "other"]);
469
+ expect(record.toJSON()).toMatchObject({
470
+ _type: "CoMap",
471
+ height: 5,
472
+ id: expect.any(String),
473
+ other: 3,
474
+ });
475
+ });
476
+
477
+ class TestRecord2 extends CoMap.Record(co.number) {}
478
+
479
+ test("Construction with index signature (shorthand)", async () => {
480
+ const me = await Account.create({
481
+ creationProps: { name: "Hermes Puggington" },
482
+ crypto: Crypto,
428
483
  });
429
484
 
430
- const record = new TestRecord(
485
+ const record = TestRecord2.create(
431
486
  {
432
487
  height: 5,
433
488
  other: 3,
434
489
  },
435
- { owner: me }
490
+ { owner: me },
436
491
  );
437
492
 
438
493
  expect(record.height).toEqual(5);
@@ -441,4 +496,37 @@ describe("CoMap resolution", async () => {
441
496
  expect(record._raw.get("other")).toEqual(3);
442
497
  expect(Object.keys(record)).toEqual(["height", "other"]);
443
498
  });
499
+
500
+ class TestRecordRef extends CoMap.Record(co.ref(TwiceNestedMap)) {}
501
+
502
+ test("Construction with index signature ref", async () => {
503
+ const me = await Account.create({
504
+ creationProps: { name: "Hermes Puggington" },
505
+ crypto: Crypto,
506
+ });
507
+
508
+ const record = TestRecordRef.create(
509
+ {
510
+ firstNested: TwiceNestedMap.create(
511
+ { taste: "sour" },
512
+ { owner: me },
513
+ ),
514
+ secondNested: TwiceNestedMap.create(
515
+ { taste: "sweet" },
516
+ { owner: me },
517
+ ),
518
+ },
519
+ { owner: me },
520
+ );
521
+
522
+ expect(record.firstNested?.taste).toEqual("sour");
523
+ expect(record.firstNested?.id).toBeDefined();
524
+ expect(record.secondNested?.taste).toEqual("sweet");
525
+ expect(record.secondNested?.id).toBeDefined();
526
+ expect(Object.keys(record)).toEqual(["firstNested", "secondNested"]);
527
+ expect(Object.keys(record._refs)).toEqual([
528
+ "firstNested",
529
+ "secondNested",
530
+ ]);
531
+ });
444
532
  });