jazz-tools 0.7.0-alpha.9 → 0.7.3

Sign up to get free protection for your applications and to get access to all the features.
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
  });