jazz-tools 0.7.0-alpha.0

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 (85) hide show
  1. package/.eslintrc.cjs +24 -0
  2. package/.turbo/turbo-build.log +24 -0
  3. package/CHANGELOG.md +42 -0
  4. package/LICENSE.txt +19 -0
  5. package/README.md +3 -0
  6. package/dist/coValueInterfaces.js +8 -0
  7. package/dist/coValueInterfaces.js.map +1 -0
  8. package/dist/coValues/account/account.js +11 -0
  9. package/dist/coValues/account/account.js.map +1 -0
  10. package/dist/coValues/account/accountOf.js +150 -0
  11. package/dist/coValues/account/accountOf.js.map +1 -0
  12. package/dist/coValues/account/migration.js +4 -0
  13. package/dist/coValues/account/migration.js.map +1 -0
  14. package/dist/coValues/coList/coList.js +2 -0
  15. package/dist/coValues/coList/coList.js.map +1 -0
  16. package/dist/coValues/coList/coListOf.js +235 -0
  17. package/dist/coValues/coList/coListOf.js.map +1 -0
  18. package/dist/coValues/coList/internalDocs.js +2 -0
  19. package/dist/coValues/coList/internalDocs.js.map +1 -0
  20. package/dist/coValues/coMap/coMap.js +2 -0
  21. package/dist/coValues/coMap/coMap.js.map +1 -0
  22. package/dist/coValues/coMap/coMapOf.js +262 -0
  23. package/dist/coValues/coMap/coMapOf.js.map +1 -0
  24. package/dist/coValues/coMap/internalDocs.js +2 -0
  25. package/dist/coValues/coMap/internalDocs.js.map +1 -0
  26. package/dist/coValues/coStream/coStream.js +2 -0
  27. package/dist/coValues/coStream/coStream.js.map +1 -0
  28. package/dist/coValues/coStream/coStreamOf.js +244 -0
  29. package/dist/coValues/coStream/coStreamOf.js.map +1 -0
  30. package/dist/coValues/construction.js +34 -0
  31. package/dist/coValues/construction.js.map +1 -0
  32. package/dist/coValues/extensions/imageDef.js +36 -0
  33. package/dist/coValues/extensions/imageDef.js.map +1 -0
  34. package/dist/coValues/group/group.js +2 -0
  35. package/dist/coValues/group/group.js.map +1 -0
  36. package/dist/coValues/group/groupOf.js +109 -0
  37. package/dist/coValues/group/groupOf.js.map +1 -0
  38. package/dist/coValues/resolution.js +66 -0
  39. package/dist/coValues/resolution.js.map +1 -0
  40. package/dist/errors.js +2 -0
  41. package/dist/errors.js.map +1 -0
  42. package/dist/index.js +31 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/refs.js +95 -0
  45. package/dist/refs.js.map +1 -0
  46. package/dist/schemaHelpers.js +14 -0
  47. package/dist/schemaHelpers.js.map +1 -0
  48. package/dist/subscriptionScope.js +81 -0
  49. package/dist/subscriptionScope.js.map +1 -0
  50. package/dist/tests/coList.test.js +207 -0
  51. package/dist/tests/coList.test.js.map +1 -0
  52. package/dist/tests/coMap.test.js +238 -0
  53. package/dist/tests/coMap.test.js.map +1 -0
  54. package/dist/tests/coStream.test.js +263 -0
  55. package/dist/tests/coStream.test.js.map +1 -0
  56. package/dist/tests/types.test.js +33 -0
  57. package/dist/tests/types.test.js.map +1 -0
  58. package/package.json +23 -0
  59. package/src/coValueInterfaces.ts +105 -0
  60. package/src/coValues/account/account.ts +106 -0
  61. package/src/coValues/account/accountOf.ts +284 -0
  62. package/src/coValues/account/migration.ts +12 -0
  63. package/src/coValues/coList/coList.ts +57 -0
  64. package/src/coValues/coList/coListOf.ts +377 -0
  65. package/src/coValues/coList/internalDocs.ts +1 -0
  66. package/src/coValues/coMap/coMap.ts +110 -0
  67. package/src/coValues/coMap/coMapOf.ts +451 -0
  68. package/src/coValues/coMap/internalDocs.ts +1 -0
  69. package/src/coValues/coStream/coStream.ts +63 -0
  70. package/src/coValues/coStream/coStreamOf.ts +404 -0
  71. package/src/coValues/construction.ts +110 -0
  72. package/src/coValues/extensions/imageDef.ts +51 -0
  73. package/src/coValues/group/group.ts +27 -0
  74. package/src/coValues/group/groupOf.ts +183 -0
  75. package/src/coValues/resolution.ts +111 -0
  76. package/src/errors.ts +1 -0
  77. package/src/index.ts +68 -0
  78. package/src/refs.ts +128 -0
  79. package/src/schemaHelpers.ts +72 -0
  80. package/src/subscriptionScope.ts +118 -0
  81. package/src/tests/coList.test.ts +283 -0
  82. package/src/tests/coMap.test.ts +357 -0
  83. package/src/tests/coStream.test.ts +415 -0
  84. package/src/tests/types.test.ts +37 -0
  85. package/tsconfig.json +15 -0
@@ -0,0 +1,283 @@
1
+ import { expect, describe, test, beforeEach } from "vitest";
2
+
3
+ import { webcrypto } from "node:crypto";
4
+ import { connectedPeers } from "cojson/src/streamUtils.js";
5
+ import { newRandomSessionID } from "cojson/src/coValueCore.js";
6
+ import { Effect, Queue } from "effect";
7
+ import { Co, S, Account, jazzReady } from "..";
8
+
9
+ if (!("crypto" in globalThis)) {
10
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
+ (globalThis as any).crypto = webcrypto;
12
+ }
13
+
14
+ beforeEach(async () => {
15
+ await jazzReady;
16
+ });
17
+
18
+ describe("Simple CoList operations", async () => {
19
+ const me = await Account.create({
20
+ name: "Hermes Puggington",
21
+ });
22
+
23
+ class TestList extends Co.list(S.string).as<TestList>() {}
24
+
25
+ const list = new TestList(["bread", "butter", "onion"], { owner: me });
26
+
27
+ test("Construction", () => {
28
+ expect(list[0]).toBe("bread");
29
+ expect(list[1]).toBe("butter");
30
+ expect(list[2]).toBe("onion");
31
+ expect(list._raw.asArray()).toEqual(["bread", "butter", "onion"]);
32
+ expect(list.length).toBe(3);
33
+ expect(list.map((item) => item.toUpperCase())).toEqual([
34
+ "BREAD",
35
+ "BUTTER",
36
+ "ONION",
37
+ ]);
38
+ });
39
+
40
+ describe("Mutation", () => {
41
+ test("assignment", () => {
42
+ const list = new TestList(["bread", "butter", "onion"], {
43
+ owner: me,
44
+ });
45
+ list[1] = "margarine";
46
+ expect(list._raw.asArray()).toEqual([
47
+ "bread",
48
+ "margarine",
49
+ "onion",
50
+ ]);
51
+ expect(list[1]).toBe("margarine");
52
+ });
53
+
54
+ test("push", () => {
55
+ const list = new TestList(["bread", "butter", "onion"], {
56
+ owner: me,
57
+ });
58
+ list.push("cheese");
59
+ expect(list[3]).toBe("cheese");
60
+ expect(list._raw.asArray()).toEqual([
61
+ "bread",
62
+ "butter",
63
+ "onion",
64
+ "cheese",
65
+ ]);
66
+ });
67
+
68
+ test("unshift", () => {
69
+ const list = new TestList(["bread", "butter", "onion"], {
70
+ owner: me,
71
+ });
72
+ list.unshift("lettuce");
73
+ expect(list[0]).toBe("lettuce");
74
+ expect(list._raw.asArray()).toEqual([
75
+ "lettuce",
76
+ "bread",
77
+ "butter",
78
+ "onion",
79
+ ]);
80
+ });
81
+
82
+ test("pop", () => {
83
+ const list = new TestList(["bread", "butter", "onion"], {
84
+ owner: me,
85
+ });
86
+ expect(list.pop()).toBe("onion");
87
+ expect(list.length).toBe(2);
88
+ expect(list._raw.asArray()).toEqual(["bread", "butter"]);
89
+ });
90
+
91
+ test("shift", () => {
92
+ const list = new TestList(["bread", "butter", "onion"], {
93
+ owner: me,
94
+ });
95
+ expect(list.shift()).toBe("bread");
96
+ expect(list.length).toBe(2);
97
+ expect(list._raw.asArray()).toEqual(["butter", "onion"]);
98
+ });
99
+
100
+ test("splice", () => {
101
+ const list = new TestList(["bread", "butter", "onion"], {
102
+ owner: me,
103
+ });
104
+ list.splice(1, 1, "salt", "pepper");
105
+ expect(list.length).toBe(4);
106
+ expect(list._raw.asArray()).toEqual([
107
+ "bread",
108
+ "salt",
109
+ "pepper",
110
+ "onion",
111
+ ]);
112
+ });
113
+ });
114
+ });
115
+
116
+ describe("CoList resolution", async () => {
117
+ class TwiceNestedList extends Co.list(S.string).as<TwiceNestedList>() {
118
+ joined() {
119
+ return this.join(",");
120
+ }
121
+ }
122
+
123
+ class NestedList extends Co.list(TwiceNestedList).as<NestedList>() {}
124
+
125
+ class TestList extends Co.list(NestedList).as<TestList>() {}
126
+
127
+ const initNodeAndList = async () => {
128
+ const me = await Account.create({
129
+ name: "Hermes Puggington",
130
+ });
131
+
132
+ const list = new TestList(
133
+ [
134
+ new NestedList(
135
+ [new TwiceNestedList(["a", "b"], { owner: me })],
136
+ { owner: me }
137
+ ),
138
+ new NestedList(
139
+ [new TwiceNestedList(["c", "d"], { owner: me })],
140
+ { owner: me }
141
+ ),
142
+ ],
143
+ { owner: me }
144
+ );
145
+
146
+ return { me, list };
147
+ };
148
+
149
+ test("Construction", async () => {
150
+ const { list } = await initNodeAndList();
151
+
152
+ expect(list[0]?.[0]?.[0]).toBe("a");
153
+ expect(list[0]?.[0]?.joined()).toBe("a,b");
154
+ expect(list[0]?.[0]?.id).toBeDefined();
155
+ expect(list[1]?.[0]?.[0]).toBe("c");
156
+ });
157
+
158
+ test("Loading and availability", async () => {
159
+ const { me, list } = await initNodeAndList();
160
+
161
+ const [initialAsPeer, secondPeer] = connectedPeers(
162
+ "initial",
163
+ "second",
164
+ { peer1role: "server", peer2role: "client" }
165
+ );
166
+ me._raw.core.node.syncManager.addPeer(secondPeer);
167
+ const meOnSecondPeer = await Account.become({
168
+ accountID: me.id,
169
+ accountSecret: me._raw.agentSecret,
170
+ peersToLoadFrom: [initialAsPeer],
171
+ sessionID: newRandomSessionID(me.id as any),
172
+ });
173
+
174
+ const loadedList = await TestList.load(list.id, { as: meOnSecondPeer });
175
+
176
+ expect(loadedList?.[0]).toBe(undefined);
177
+ expect(loadedList?._refs[0]?.id).toEqual(list[0]!.id);
178
+
179
+ const loadedNestedList = await NestedList.load(list[0]!.id, {
180
+ as: meOnSecondPeer,
181
+ });
182
+
183
+ expect(loadedList?.[0]).toBeDefined();
184
+ expect(loadedList?.[0]?.[0]).toBeUndefined();
185
+ expect(loadedList?.[0]?._refs[0]?.id).toEqual(list[0]![0]!.id);
186
+ expect(loadedList?._refs[0]?.value).toEqual(loadedNestedList);
187
+
188
+ const loadedTwiceNestedList = await TwiceNestedList.load(
189
+ list[0]![0]!.id,
190
+ { as: meOnSecondPeer }
191
+ );
192
+
193
+ expect(loadedList?.[0]?.[0]).toBeDefined();
194
+ expect(loadedList?.[0]?.[0]?.[0]).toBe("a");
195
+ expect(loadedList?.[0]?.[0]?.joined()).toBe("a,b");
196
+ expect(loadedList?.[0]?._refs[0]?.id).toEqual(list[0]?.[0]?.id);
197
+ expect(loadedList?.[0]?._refs[0]?.value).toEqual(
198
+ loadedTwiceNestedList
199
+ );
200
+
201
+ const otherNestedList = new NestedList(
202
+ [new TwiceNestedList(["e", "f"], { owner: meOnSecondPeer })],
203
+ { owner: meOnSecondPeer }
204
+ );
205
+
206
+ loadedList![0] = otherNestedList;
207
+ expect(loadedList?.[0]).toEqual(otherNestedList);
208
+ expect(loadedList?._refs[0]?.id).toEqual(otherNestedList.id);
209
+ });
210
+
211
+ test("Subscription & auto-resolution", async () => {
212
+ const { me, list } = await initNodeAndList();
213
+
214
+ const [initialAsPeer, secondPeer] = connectedPeers(
215
+ "initial",
216
+ "second",
217
+ { peer1role: "server", peer2role: "client" }
218
+ );
219
+ me._raw.core.node.syncManager.addPeer(secondPeer);
220
+ const meOnSecondPeer = await Account.become({
221
+ accountID: me.id,
222
+ accountSecret: me._raw.agentSecret,
223
+ peersToLoadFrom: [initialAsPeer],
224
+ sessionID: newRandomSessionID(me.id as any),
225
+ });
226
+
227
+ await Effect.runPromise(
228
+ Effect.gen(function* ($) {
229
+ const queue = yield* $(Queue.unbounded<TestList>());
230
+
231
+ TestList.subscribe(
232
+ list.id,
233
+ { as: meOnSecondPeer },
234
+ (subscribedList) => {
235
+ console.log(
236
+ "subscribedList?.[0]?.[0]?.[0]",
237
+ subscribedList?.[0]?.[0]?.[0]
238
+ );
239
+ Effect.runPromise(Queue.offer(queue, subscribedList));
240
+ }
241
+ );
242
+
243
+ const update1 = yield* $(Queue.take(queue));
244
+ expect(update1?.[0]).toEqual(undefined);
245
+
246
+ const update2 = yield* $(Queue.take(queue));
247
+ expect(update2?.[0]).toBeDefined();
248
+ expect(update2?.[0]?.[0]).toBeUndefined();
249
+
250
+ const update3 = yield* $(Queue.take(queue));
251
+ expect(update3?.[0]?.[0]).toBeDefined();
252
+ expect(update3?.[0]?.[0]?.[0]).toBe("a");
253
+ expect(update3?.[0]?.[0]?.joined()).toBe("a,b");
254
+
255
+ update3[0]![0]![0] = "x";
256
+
257
+ const update4 = yield* $(Queue.take(queue));
258
+ expect(update4?.[0]?.[0]?.[0]).toBe("x");
259
+
260
+ // When assigning a new nested value, we get an update
261
+
262
+ const newTwiceNestedList = new TwiceNestedList(["y", "z"], {
263
+ owner: meOnSecondPeer,
264
+ });
265
+
266
+ const newNestedList = new NestedList([newTwiceNestedList], {
267
+ owner: meOnSecondPeer,
268
+ });
269
+
270
+ update4[0] = newNestedList;
271
+
272
+ const update5 = yield* $(Queue.take(queue));
273
+ expect(update5?.[0]?.[0]?.[0]).toBe("y");
274
+ expect(update5?.[0]?.[0]?.joined()).toBe("y,z");
275
+
276
+ // we get updates when the new nested value changes
277
+ newTwiceNestedList[0] = "w";
278
+ const update6 = yield* $(Queue.take(queue));
279
+ expect(update6?.[0]?.[0]?.[0]).toBe("w");
280
+ })
281
+ );
282
+ });
283
+ });
@@ -0,0 +1,357 @@
1
+ import { expect, describe, test, beforeEach } from "vitest";
2
+
3
+ import { webcrypto } from "node:crypto";
4
+ import { connectedPeers } from "cojson/src/streamUtils.js";
5
+ import { newRandomSessionID } from "cojson/src/coValueCore.js";
6
+ import { Effect, Queue } from "effect";
7
+ import { Co, S, Account, jazzReady } from "..";
8
+
9
+ if (!("crypto" in globalThis)) {
10
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
+ (globalThis as any).crypto = webcrypto;
12
+ }
13
+
14
+ beforeEach(async () => {
15
+ await jazzReady;
16
+ });
17
+
18
+ describe("Simple CoMap operations", async () => {
19
+ const me = await Account.create({
20
+ name: "Hermes Puggington",
21
+ });
22
+
23
+ class TestMap extends Co.map({
24
+ color: S.string,
25
+ height: S.number,
26
+ birthday: S.Date,
27
+ name: S.optional(S.string),
28
+ }).as<TestMap>() {
29
+ get roughColor() {
30
+ return this.color + "ish";
31
+ }
32
+ }
33
+
34
+ const birthday = new Date();
35
+
36
+ const map = new TestMap(
37
+ {
38
+ color: "red",
39
+ height: 10,
40
+ birthday: birthday,
41
+ },
42
+ { owner: me }
43
+ );
44
+
45
+ test("Construction", () => {
46
+ expect(map.color).toEqual("red");
47
+ expect(map.roughColor).toEqual("redish");
48
+ expect(map.height).toEqual(10);
49
+ expect(map.birthday).toEqual(birthday);
50
+ expect(map._raw.get("birthday")).toEqual(birthday.toISOString());
51
+ });
52
+
53
+ describe("Mutation", () => {
54
+ test("assignment", () => {
55
+ map.color = "blue";
56
+ expect(map.color).toEqual("blue");
57
+ expect(map._raw.get("color")).toEqual("blue");
58
+ const newBirthday = new Date();
59
+ map.birthday = newBirthday;
60
+ expect(map.birthday).toEqual(newBirthday);
61
+ expect(map._raw.get("birthday")).toEqual(
62
+ newBirthday.toISOString()
63
+ );
64
+
65
+ Object.assign(map, { color: "green", height: 20 });
66
+ expect(map.color).toEqual("green");
67
+ expect(map._raw.get("color")).toEqual("green");
68
+ expect(map.height).toEqual(20);
69
+ expect(map._raw.get("height")).toEqual(20);
70
+
71
+ map.name = "Secret name";
72
+ expect(map.name).toEqual("Secret name");
73
+ delete map.name;
74
+ expect(map.name).toEqual(undefined);
75
+ });
76
+ });
77
+ });
78
+
79
+ describe("CoMap resolution", async () => {
80
+ class TwiceNestedMap extends Co.map({
81
+ taste: S.string,
82
+ }).as<TwiceNestedMap>() {}
83
+
84
+ class NestedMap extends Co.map({
85
+ name: S.string,
86
+ twiceNested: TwiceNestedMap,
87
+ }).as<NestedMap>() {
88
+ get fancyName() {
89
+ return "Sir " + this.name;
90
+ }
91
+ }
92
+
93
+ class TestMap extends Co.map({
94
+ color: S.string,
95
+ height: S.number,
96
+ nested: NestedMap,
97
+ }).as<TestMap>() {
98
+ get roughColor() {
99
+ return this.color + "ish";
100
+ }
101
+ }
102
+
103
+ const initNodeAndMap = async () => {
104
+ const me = await Account.create({
105
+ name: "Hermes Puggington",
106
+ });
107
+
108
+ const map = new TestMap(
109
+ {
110
+ color: "red",
111
+ height: 10,
112
+ nested: new NestedMap(
113
+ {
114
+ name: "nested",
115
+ twiceNested: new TwiceNestedMap(
116
+ { taste: "sour" },
117
+ { owner: me }
118
+ ),
119
+ },
120
+ { owner: me }
121
+ ),
122
+ },
123
+ { owner: me }
124
+ );
125
+
126
+ return { me, map };
127
+ };
128
+
129
+ test("Construction", async () => {
130
+ const { map } = await initNodeAndMap();
131
+
132
+ // const test: Schema.Schema.To<typeof NestedMap>
133
+
134
+ expect(map.color).toEqual("red");
135
+ expect(map.roughColor).toEqual("redish");
136
+ expect(map.height).toEqual(10);
137
+ expect(map.nested?.name).toEqual("nested");
138
+ expect(map.nested?.fancyName).toEqual("Sir nested");
139
+ expect(map.nested?.id).toBeDefined();
140
+ expect(map.nested?.twiceNested?.taste).toEqual("sour");
141
+ });
142
+
143
+ test("Loading and availability", async () => {
144
+ const { me, map } = await initNodeAndMap();
145
+ const [initialAsPeer, secondPeer] = connectedPeers(
146
+ "initial",
147
+ "second",
148
+ { peer1role: "server", peer2role: "client" }
149
+ );
150
+ me._raw.core.node.syncManager.addPeer(secondPeer);
151
+ const meOnSecondPeer = await Account.become({
152
+ accountID: me.id,
153
+ accountSecret: me._raw.agentSecret,
154
+ peersToLoadFrom: [initialAsPeer],
155
+ sessionID: newRandomSessionID(me.id as any),
156
+ });
157
+
158
+ const loadedMap = await TestMap.load(map.id, { as: meOnSecondPeer });
159
+
160
+ expect(loadedMap?.color).toEqual("red");
161
+ expect(loadedMap?.height).toEqual(10);
162
+ expect(loadedMap?.nested).toEqual(undefined);
163
+ expect(loadedMap?._refs.nested?.id).toEqual(map.nested?.id);
164
+ expect(loadedMap?._refs.nested?.value).toEqual(undefined);
165
+
166
+ const loadedNestedMap = await NestedMap.load(map.nested!.id, {
167
+ as: meOnSecondPeer,
168
+ });
169
+
170
+ expect(loadedMap?.nested?.name).toEqual("nested");
171
+ expect(loadedMap?.nested.fancyName).toEqual("Sir nested");
172
+ expect(loadedMap?._refs.nested?.value).toEqual(loadedNestedMap);
173
+ expect(loadedMap?.nested?.twiceNested?.taste).toEqual(undefined);
174
+
175
+ const loadedTwiceNestedMap = await TwiceNestedMap.load(
176
+ map.nested!.twiceNested!.id,
177
+ { as: meOnSecondPeer }
178
+ );
179
+
180
+ expect(loadedMap?.nested?.twiceNested?.taste).toEqual("sour");
181
+ expect(loadedMap?.nested?._refs.twiceNested?.value).toEqual(
182
+ loadedTwiceNestedMap
183
+ );
184
+
185
+ const otherNestedMap = new NestedMap(
186
+ {
187
+ name: "otherNested",
188
+ twiceNested: new TwiceNestedMap(
189
+ { taste: "sweet" },
190
+ { owner: meOnSecondPeer }
191
+ ),
192
+ },
193
+ { owner: meOnSecondPeer }
194
+ );
195
+
196
+ loadedMap!.nested = otherNestedMap;
197
+ expect(loadedMap?.nested?.name).toEqual("otherNested");
198
+ expect(loadedMap?._refs.nested?.id).toEqual(otherNestedMap.id);
199
+ expect(loadedMap?._refs.nested?.value).toEqual(otherNestedMap);
200
+ expect(loadedMap?.nested?.twiceNested?.taste).toEqual("sweet");
201
+ expect(loadedMap?.nested?._refs.twiceNested?.value).toBeDefined();
202
+ });
203
+
204
+ test("Subscription & auto-resolution", async () => {
205
+ const { me, map } = await initNodeAndMap();
206
+
207
+ const [initialAsPeer, secondAsPeer] = connectedPeers(
208
+ "initial",
209
+ "second",
210
+ { peer1role: "server", peer2role: "client" }
211
+ );
212
+
213
+ me._raw.core.node.syncManager.addPeer(secondAsPeer);
214
+
215
+ const meOnSecondPeer = await Account.become({
216
+ accountID: me.id,
217
+ accountSecret: me._raw.agentSecret,
218
+ peersToLoadFrom: [initialAsPeer],
219
+ sessionID: newRandomSessionID(me.id as any),
220
+ });
221
+
222
+ await Effect.runPromise(
223
+ Effect.gen(function* ($) {
224
+ const queue = yield* $(Queue.unbounded<TestMap>());
225
+
226
+ TestMap.subscribe(
227
+ map.id,
228
+ { as: meOnSecondPeer },
229
+ (subscribedMap) => {
230
+ console.log(
231
+ "subscribedMap.nested?.twiceNested?.taste",
232
+ subscribedMap.nested?.twiceNested?.taste
233
+ );
234
+ Effect.runPromise(Queue.offer(queue, subscribedMap));
235
+ }
236
+ );
237
+
238
+ const update1 = yield* $(Queue.take(queue));
239
+ expect(update1.nested).toEqual(undefined);
240
+
241
+ const update2 = yield* $(Queue.take(queue));
242
+ expect(update2.nested?.name).toEqual("nested");
243
+
244
+ map.nested!.name = "nestedUpdated";
245
+
246
+ const _ = yield* $(Queue.take(queue));
247
+ const update3 = yield* $(Queue.take(queue));
248
+ expect(update3.nested?.name).toEqual("nestedUpdated");
249
+
250
+ const oldTwiceNested = update3.nested!.twiceNested;
251
+ expect(oldTwiceNested?.taste).toEqual("sour");
252
+
253
+ // When assigning a new nested value, we get an update
254
+ const newTwiceNested = new TwiceNestedMap(
255
+ {
256
+ taste: "sweet",
257
+ },
258
+ { owner: meOnSecondPeer }
259
+ );
260
+
261
+ const newNested = new NestedMap(
262
+ {
263
+ name: "newNested",
264
+ twiceNested: newTwiceNested,
265
+ },
266
+ { owner: meOnSecondPeer }
267
+ );
268
+
269
+ update3.nested = newNested;
270
+
271
+ yield* $(Queue.take(queue));
272
+ // const update4 = yield* $(Queue.take(queue));
273
+ const update4b = yield* $(Queue.take(queue));
274
+
275
+ expect(update4b.nested?.name).toEqual("newNested");
276
+ expect(update4b.nested?.twiceNested?.taste).toEqual("sweet");
277
+
278
+ // we get updates when the new nested value changes
279
+ newTwiceNested.taste = "salty";
280
+ const update5 = yield* $(Queue.take(queue));
281
+ expect(update5.nested?.twiceNested?.taste).toEqual("salty");
282
+
283
+ newTwiceNested.taste = "umami";
284
+ const update6 = yield* $(Queue.take(queue));
285
+ expect(update6.nested?.twiceNested?.taste).toEqual("umami");
286
+ })
287
+ );
288
+ });
289
+
290
+ class TestMapWithOptionalRef extends Co.map({
291
+ color: S.string,
292
+ nested: S.optional(NestedMap),
293
+ }).as<TestMapWithOptionalRef>() {}
294
+
295
+ test("Construction with optional", async () => {
296
+ const me = await Account.create({
297
+ name: "Hermes Puggington",
298
+ });
299
+
300
+ const mapWithout = new TestMapWithOptionalRef(
301
+ {
302
+ color: "red",
303
+ },
304
+ { owner: me }
305
+ );
306
+
307
+ expect(mapWithout.color).toEqual("red");
308
+ expect(mapWithout.nested).toEqual(undefined);
309
+
310
+ const mapWith = new TestMapWithOptionalRef(
311
+ {
312
+ color: "red",
313
+ nested: new NestedMap(
314
+ {
315
+ name: "wow!",
316
+ twiceNested: new TwiceNestedMap(
317
+ { taste: "sour" },
318
+ { owner: me }
319
+ ),
320
+ },
321
+ { owner: me }
322
+ ),
323
+ },
324
+ { owner: me }
325
+ );
326
+
327
+ expect(mapWith.color).toEqual("red");
328
+ expect(mapWith.nested?.name).toEqual("wow!");
329
+ expect(mapWith.nested?.fancyName).toEqual("Sir wow!");
330
+ expect(mapWith.nested?._raw).toBeDefined();
331
+ });
332
+
333
+ class TestRecord extends Co.map(
334
+ { color: S.string },
335
+ { key: S.string, value: S.string }
336
+ ).as<TestRecord>() {}
337
+
338
+ test("Construction with index signature", async () => {
339
+ const me = await Account.create({
340
+ name: "Hermes Puggington",
341
+ });
342
+
343
+ const record = new TestRecord(
344
+ {
345
+ color: "red",
346
+ other: "wild",
347
+ },
348
+ { owner: me }
349
+ );
350
+
351
+ expect(record.color).toEqual("red");
352
+ expect(record._raw.get("color")).toEqual("red");
353
+ expect(record.other).toEqual("wild");
354
+ expect(record._raw.get("other")).toEqual("wild");
355
+ expect(Object.keys(record)).toEqual(["color", "other"]);
356
+ });
357
+ });