jazz-tools 0.7.0-alpha.9 → 0.7.3

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 (73) hide show
  1. package/.eslintrc.cjs +3 -10
  2. package/.prettierrc.js +9 -0
  3. package/.turbo/turbo-build.log +14 -15
  4. package/.turbo/turbo-lint.log +4 -0
  5. package/.turbo/turbo-test.log +140 -0
  6. package/CHANGELOG.md +331 -27
  7. package/LICENSE.txt +1 -1
  8. package/README.md +10 -2
  9. package/dist/coValues/account.js +86 -41
  10. package/dist/coValues/account.js.map +1 -1
  11. package/dist/coValues/coList.js +75 -48
  12. package/dist/coValues/coList.js.map +1 -1
  13. package/dist/coValues/coMap.js +167 -44
  14. package/dist/coValues/coMap.js.map +1 -1
  15. package/dist/coValues/coStream.js +192 -35
  16. package/dist/coValues/coStream.js.map +1 -1
  17. package/dist/coValues/deepLoading.js +60 -0
  18. package/dist/coValues/deepLoading.js.map +1 -0
  19. package/dist/coValues/extensions/imageDef.js +10 -7
  20. package/dist/coValues/extensions/imageDef.js.map +1 -1
  21. package/dist/coValues/group.js +73 -13
  22. package/dist/coValues/group.js.map +1 -1
  23. package/dist/coValues/interfaces.js +58 -35
  24. package/dist/coValues/interfaces.js.map +1 -1
  25. package/dist/implementation/devtoolsFormatters.js +114 -0
  26. package/dist/implementation/devtoolsFormatters.js.map +1 -0
  27. package/dist/implementation/refs.js +58 -18
  28. package/dist/implementation/refs.js.map +1 -1
  29. package/dist/implementation/schema.js +58 -0
  30. package/dist/implementation/schema.js.map +1 -0
  31. package/dist/implementation/subscriptionScope.js +19 -1
  32. package/dist/implementation/subscriptionScope.js.map +1 -1
  33. package/dist/implementation/symbols.js +5 -0
  34. package/dist/implementation/symbols.js.map +1 -0
  35. package/dist/index.js +4 -5
  36. package/dist/index.js.map +1 -1
  37. package/dist/internal.js +5 -2
  38. package/dist/internal.js.map +1 -1
  39. package/dist/tests/coList.test.js +51 -48
  40. package/dist/tests/coList.test.js.map +1 -1
  41. package/dist/tests/coMap.test.js +131 -74
  42. package/dist/tests/coMap.test.js.map +1 -1
  43. package/dist/tests/coStream.test.js +56 -41
  44. package/dist/tests/coStream.test.js.map +1 -1
  45. package/dist/tests/deepLoading.test.js +188 -0
  46. package/dist/tests/deepLoading.test.js.map +1 -0
  47. package/dist/tests/groupsAndAccounts.test.js +83 -0
  48. package/dist/tests/groupsAndAccounts.test.js.map +1 -0
  49. package/package.json +17 -9
  50. package/src/coValues/account.ts +186 -128
  51. package/src/coValues/coList.ts +156 -107
  52. package/src/coValues/coMap.ts +272 -148
  53. package/src/coValues/coStream.ts +388 -87
  54. package/src/coValues/deepLoading.ts +229 -0
  55. package/src/coValues/extensions/imageDef.ts +17 -13
  56. package/src/coValues/group.ts +166 -59
  57. package/src/coValues/interfaces.ts +189 -160
  58. package/src/implementation/devtoolsFormatters.ts +110 -0
  59. package/src/implementation/inspect.ts +1 -1
  60. package/src/implementation/refs.ts +80 -28
  61. package/src/implementation/schema.ts +141 -0
  62. package/src/implementation/subscriptionScope.ts +48 -12
  63. package/src/implementation/symbols.ts +11 -0
  64. package/src/index.ts +19 -8
  65. package/src/internal.ts +7 -3
  66. package/src/tests/coList.test.ts +77 -62
  67. package/src/tests/coMap.test.ts +201 -114
  68. package/src/tests/coStream.test.ts +113 -84
  69. package/src/tests/deepLoading.test.ts +304 -0
  70. package/src/tests/groupsAndAccounts.test.ts +91 -0
  71. package/dist/implementation/encoding.js +0 -26
  72. package/dist/implementation/encoding.js.map +0 -1
  73. 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,36 +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;
162
- extra = val.ref(() => TestMap)
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>();
163
165
  }
164
- 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
+ >;
165
171
  });
166
172
 
167
173
  describe("CoMap resolution", async () => {
168
- class TwiceNestedMap extends CoMap<TwiceNestedMap> {
169
- taste = val.string;
174
+ class TwiceNestedMap extends CoMap {
175
+ taste = co.string;
170
176
  }
171
177
 
172
- class NestedMap extends CoMap<NestedMap> {
173
- name = val.string;
174
- twiceNested = val.ref(() => TwiceNestedMap);
178
+ class NestedMap extends CoMap {
179
+ name = co.string;
180
+ twiceNested = co.ref(TwiceNestedMap);
175
181
 
176
182
  get _fancyName() {
177
183
  return "Sir " + this.name;
178
184
  }
179
185
  }
180
186
 
181
- class TestMap extends CoMap<TestMap> {
182
- color = val.string;
183
- height = val.number;
184
- nested = val.ref(() => NestedMap);
187
+ class TestMap extends CoMap {
188
+ color = co.string;
189
+ height = co.number;
190
+ nested = co.ref(NestedMap);
185
191
 
186
192
  get _roughColor() {
187
193
  return this.color + "ish";
@@ -190,25 +196,26 @@ describe("CoMap resolution", async () => {
190
196
 
191
197
  const initNodeAndMap = async () => {
192
198
  const me = await Account.create({
193
- name: "Hermes Puggington",
199
+ creationProps: { name: "Hermes Puggington" },
200
+ crypto: Crypto,
194
201
  });
195
202
 
196
- const map = new TestMap(
203
+ const map = TestMap.create(
197
204
  {
198
205
  color: "red",
199
206
  height: 10,
200
- nested: new NestedMap(
207
+ nested: NestedMap.create(
201
208
  {
202
209
  name: "nested",
203
- twiceNested: new TwiceNestedMap(
210
+ twiceNested: TwiceNestedMap.create(
204
211
  { taste: "sour" },
205
- { owner: me }
212
+ { owner: me },
206
213
  ),
207
214
  },
208
- { owner: me }
215
+ { owner: me },
209
216
  ),
210
217
  },
211
- { owner: me }
218
+ { owner: me },
212
219
  );
213
220
 
214
221
  return { me, map };
@@ -233,17 +240,22 @@ describe("CoMap resolution", async () => {
233
240
  const [initialAsPeer, secondPeer] = connectedPeers(
234
241
  "initial",
235
242
  "second",
236
- { peer1role: "server", peer2role: "client" }
243
+ { peer1role: "server", peer2role: "client" },
237
244
  );
245
+ if (!isControlledAccount(me)) {
246
+ throw "me is not a controlled account";
247
+ }
238
248
  me._raw.core.node.syncManager.addPeer(secondPeer);
239
249
  const meOnSecondPeer = await Account.become({
240
250
  accountID: me.id,
241
251
  accountSecret: me._raw.agentSecret,
242
252
  peersToLoadFrom: [initialAsPeer],
253
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
243
254
  sessionID: newRandomSessionID(me.id as any),
255
+ crypto: Crypto,
244
256
  });
245
257
 
246
- const loadedMap = await TestMap.load(map.id, { as: meOnSecondPeer });
258
+ const loadedMap = await TestMap.load(map.id, meOnSecondPeer, {});
247
259
 
248
260
  expect(loadedMap?.color).toEqual("red");
249
261
  expect(loadedMap?.height).toEqual(10);
@@ -251,9 +263,11 @@ describe("CoMap resolution", async () => {
251
263
  expect(loadedMap?._refs.nested?.id).toEqual(map.nested?.id);
252
264
  expect(loadedMap?._refs.nested?.value).toEqual(null);
253
265
 
254
- const loadedNestedMap = await NestedMap.load(map.nested!.id, {
255
- as: meOnSecondPeer,
256
- });
266
+ const loadedNestedMap = await NestedMap.load(
267
+ map.nested!.id,
268
+ meOnSecondPeer,
269
+ {},
270
+ );
257
271
 
258
272
  expect(loadedMap?.nested?.name).toEqual("nested");
259
273
  expect(loadedMap?.nested?._fancyName).toEqual("Sir nested");
@@ -262,23 +276,24 @@ describe("CoMap resolution", async () => {
262
276
 
263
277
  const loadedTwiceNestedMap = await TwiceNestedMap.load(
264
278
  map.nested!.twiceNested!.id,
265
- { as: meOnSecondPeer }
279
+ meOnSecondPeer,
280
+ {},
266
281
  );
267
282
 
268
283
  expect(loadedMap?.nested?.twiceNested?.taste).toEqual("sour");
269
284
  expect(loadedMap?.nested?._refs.twiceNested?.value).toEqual(
270
- loadedTwiceNestedMap
285
+ loadedTwiceNestedMap,
271
286
  );
272
287
 
273
- const otherNestedMap = new NestedMap(
288
+ const otherNestedMap = NestedMap.create(
274
289
  {
275
290
  name: "otherNested",
276
- twiceNested: new TwiceNestedMap(
291
+ twiceNested: TwiceNestedMap.create(
277
292
  { taste: "sweet" },
278
- { owner: meOnSecondPeer }
293
+ { owner: meOnSecondPeer },
279
294
  ),
280
295
  },
281
- { owner: meOnSecondPeer }
296
+ { owner: meOnSecondPeer },
282
297
  );
283
298
 
284
299
  loadedMap!.nested = otherNestedMap;
@@ -295,16 +310,19 @@ describe("CoMap resolution", async () => {
295
310
  const [initialAsPeer, secondAsPeer] = connectedPeers(
296
311
  "initial",
297
312
  "second",
298
- { peer1role: "server", peer2role: "client" }
313
+ { peer1role: "server", peer2role: "client" },
299
314
  );
300
-
315
+ if (!isControlledAccount(me)) {
316
+ throw "me is not a controlled account";
317
+ }
301
318
  me._raw.core.node.syncManager.addPeer(secondAsPeer);
302
-
303
319
  const meOnSecondPeer = await Account.become({
304
320
  accountID: me.id,
305
321
  accountSecret: me._raw.agentSecret,
306
322
  peersToLoadFrom: [initialAsPeer],
323
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
307
324
  sessionID: newRandomSessionID(me.id as any),
325
+ crypto: Crypto,
308
326
  });
309
327
 
310
328
  await Effect.runPromise(
@@ -313,14 +331,17 @@ describe("CoMap resolution", async () => {
313
331
 
314
332
  TestMap.subscribe(
315
333
  map.id,
316
- { as: meOnSecondPeer },
334
+ meOnSecondPeer,
335
+ {},
317
336
  (subscribedMap) => {
318
337
  console.log(
319
338
  "subscribedMap.nested?.twiceNested?.taste",
320
- subscribedMap.nested?.twiceNested?.taste
339
+ subscribedMap.nested?.twiceNested?.taste,
340
+ );
341
+ void Effect.runPromise(
342
+ Queue.offer(queue, subscribedMap),
321
343
  );
322
- Effect.runPromise(Queue.offer(queue, subscribedMap));
323
- }
344
+ },
324
345
  );
325
346
 
326
347
  const update1 = yield* $(Queue.take(queue));
@@ -339,19 +360,19 @@ describe("CoMap resolution", async () => {
339
360
  expect(oldTwiceNested?.taste).toEqual("sour");
340
361
 
341
362
  // When assigning a new nested value, we get an update
342
- const newTwiceNested = new TwiceNestedMap(
363
+ const newTwiceNested = TwiceNestedMap.create(
343
364
  {
344
365
  taste: "sweet",
345
366
  },
346
- { owner: meOnSecondPeer }
367
+ { owner: meOnSecondPeer },
347
368
  );
348
369
 
349
- const newNested = new NestedMap(
370
+ const newNested = NestedMap.create(
350
371
  {
351
372
  name: "newNested",
352
373
  twiceNested: newTwiceNested,
353
374
  },
354
- { owner: meOnSecondPeer }
375
+ { owner: meOnSecondPeer },
355
376
  );
356
377
 
357
378
  update3.nested = newNested;
@@ -371,45 +392,46 @@ describe("CoMap resolution", async () => {
371
392
  newTwiceNested.taste = "umami";
372
393
  const update6 = yield* $(Queue.take(queue));
373
394
  expect(update6.nested?.twiceNested?.taste).toEqual("umami");
374
- })
395
+ }),
375
396
  );
376
397
  });
377
398
 
378
- class TestMapWithOptionalRef extends CoMap<TestMapWithOptionalRef> {
379
- color = val.string;
380
- nested? = val.ref(() => NestedMap);
399
+ class TestMapWithOptionalRef extends CoMap {
400
+ color = co.string;
401
+ nested = co.ref(NestedMap, { optional: true });
381
402
  }
382
403
 
383
404
  test("Construction with optional", async () => {
384
405
  const me = await Account.create({
385
- name: "Hermes Puggington",
406
+ creationProps: { name: "Hermes Puggington" },
407
+ crypto: Crypto,
386
408
  });
387
409
 
388
- const mapWithout = new TestMapWithOptionalRef(
410
+ const mapWithout = TestMapWithOptionalRef.create(
389
411
  {
390
412
  color: "red",
391
413
  },
392
- { owner: me }
414
+ { owner: me },
393
415
  );
394
416
 
395
417
  expect(mapWithout.color).toEqual("red");
396
418
  expect(mapWithout.nested).toEqual(undefined);
397
419
 
398
- const mapWith = new TestMapWithOptionalRef(
420
+ const mapWith = TestMapWithOptionalRef.create(
399
421
  {
400
422
  color: "red",
401
- nested: new NestedMap(
423
+ nested: NestedMap.create(
402
424
  {
403
425
  name: "wow!",
404
- twiceNested: new TwiceNestedMap(
426
+ twiceNested: TwiceNestedMap.create(
405
427
  { taste: "sour" },
406
- { owner: me }
428
+ { owner: me },
407
429
  ),
408
430
  },
409
- { owner: me }
431
+ { owner: me },
410
432
  ),
411
433
  },
412
- { owner: me }
434
+ { owner: me },
413
435
  );
414
436
 
415
437
  expect(mapWith.color).toEqual("red");
@@ -418,22 +440,54 @@ describe("CoMap resolution", async () => {
418
440
  expect(mapWith.nested?._raw).toBeDefined();
419
441
  });
420
442
 
421
- class TestRecord extends CoMap<TestRecord> {
422
- [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;
423
446
  }
447
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
424
448
  interface TestRecord extends Record<string, number> {}
425
449
 
426
450
  test("Construction with index signature", async () => {
427
451
  const me = await Account.create({
428
- 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,
429
483
  });
430
484
 
431
- const record = new TestRecord(
485
+ const record = TestRecord2.create(
432
486
  {
433
487
  height: 5,
434
488
  other: 3,
435
489
  },
436
- { owner: me }
490
+ { owner: me },
437
491
  );
438
492
 
439
493
  expect(record.height).toEqual(5);
@@ -442,4 +496,37 @@ describe("CoMap resolution", async () => {
442
496
  expect(record._raw.get("other")).toEqual(3);
443
497
  expect(Object.keys(record)).toEqual(["height", "other"]);
444
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
+ });
445
532
  });