jazz-tools 0.7.0-alpha.8 → 0.7.1

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 (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
  });