jazz-tools 0.15.14 → 0.15.16
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.
- package/.turbo/turbo-build.log +40 -44
- package/CHANGELOG.md +19 -0
- package/dist/{chunk-6CCJYSYQ.js → chunk-OSVAAVWQ.js} +107 -11
- package/dist/chunk-OSVAAVWQ.js.map +1 -0
- package/dist/index.js +357 -3
- package/dist/index.js.map +1 -1
- package/dist/testing.js +1 -1
- package/dist/tools/coValues/group.d.ts +1 -0
- package/dist/tools/coValues/group.d.ts.map +1 -1
- package/dist/tools/coValues/inbox.d.ts.map +1 -1
- package/dist/tools/coValues/interfaces.d.ts +58 -2
- package/dist/tools/coValues/interfaces.d.ts.map +1 -1
- package/dist/tools/coValues/request.d.ts +82 -0
- package/dist/tools/coValues/request.d.ts.map +1 -0
- package/dist/tools/exports.d.ts +2 -1
- package/dist/tools/exports.d.ts.map +1 -1
- package/dist/tools/lib/id.d.ts +2 -0
- package/dist/tools/lib/id.d.ts.map +1 -0
- package/dist/tools/subscribe/SubscriptionScope.d.ts +3 -2
- package/dist/tools/subscribe/SubscriptionScope.d.ts.map +1 -1
- package/dist/tools/tests/exportImport.test.d.ts +2 -0
- package/dist/tools/tests/exportImport.test.d.ts.map +1 -0
- package/dist/tools/tests/request.test.d.ts +2 -0
- package/dist/tools/tests/request.test.d.ts.map +1 -0
- package/package.json +6 -5
- package/src/tools/coValues/group.ts +1 -0
- package/src/tools/coValues/inbox.ts +4 -3
- package/src/tools/coValues/interfaces.ts +177 -2
- package/src/tools/coValues/request.ts +631 -0
- package/src/tools/exports.ts +8 -0
- package/src/tools/lib/id.ts +3 -0
- package/src/tools/subscribe/SubscriptionScope.ts +17 -10
- package/src/tools/tests/coMap.test.ts +141 -0
- package/src/tools/tests/exportImport.test.ts +526 -0
- package/src/tools/tests/request.test.ts +951 -0
- package/tsup.config.ts +0 -2
- package/dist/chunk-6CCJYSYQ.js.map +0 -1
@@ -2103,3 +2103,144 @@ describe("CoMap migration", () => {
|
|
2103
2103
|
});
|
2104
2104
|
});
|
2105
2105
|
});
|
2106
|
+
|
2107
|
+
describe("Updating a nested reference", () => {
|
2108
|
+
test("should assign a resolved optional reference and expect value is not null", async () => {
|
2109
|
+
// Define the schema similar to the server-worker-http example
|
2110
|
+
const PlaySelection = co.map({
|
2111
|
+
value: z.literal(["rock", "paper", "scissors"]),
|
2112
|
+
group: Group,
|
2113
|
+
});
|
2114
|
+
|
2115
|
+
const Player = co.map({
|
2116
|
+
account: co.account(),
|
2117
|
+
playSelection: PlaySelection.optional(),
|
2118
|
+
});
|
2119
|
+
|
2120
|
+
const Game = co.map({
|
2121
|
+
player1: Player,
|
2122
|
+
player2: Player,
|
2123
|
+
outcome: z.literal(["player1", "player2", "draw"]).optional(),
|
2124
|
+
player1Score: z.number(),
|
2125
|
+
player2Score: z.number(),
|
2126
|
+
});
|
2127
|
+
|
2128
|
+
// Create accounts for the players
|
2129
|
+
const player1Account = await createJazzTestAccount({
|
2130
|
+
creationProps: { name: "Player 1" },
|
2131
|
+
});
|
2132
|
+
const player2Account = await createJazzTestAccount({
|
2133
|
+
creationProps: { name: "Player 2" },
|
2134
|
+
});
|
2135
|
+
|
2136
|
+
// Create a game
|
2137
|
+
const game = Game.create({
|
2138
|
+
player1: Player.create({
|
2139
|
+
account: player1Account,
|
2140
|
+
}),
|
2141
|
+
player2: Player.create({
|
2142
|
+
account: player2Account,
|
2143
|
+
}),
|
2144
|
+
player1Score: 0,
|
2145
|
+
player2Score: 0,
|
2146
|
+
});
|
2147
|
+
|
2148
|
+
// Create a group for the play selection (similar to the route logic)
|
2149
|
+
const group = Group.create({ owner: Account.getMe() });
|
2150
|
+
group.addMember(player1Account, "reader");
|
2151
|
+
|
2152
|
+
// Load the game to verify the assignment worked
|
2153
|
+
const loadedGame = await Game.load(game.id, {
|
2154
|
+
resolve: {
|
2155
|
+
player1: {
|
2156
|
+
account: true,
|
2157
|
+
playSelection: true,
|
2158
|
+
},
|
2159
|
+
player2: {
|
2160
|
+
account: true,
|
2161
|
+
playSelection: true,
|
2162
|
+
},
|
2163
|
+
},
|
2164
|
+
});
|
2165
|
+
|
2166
|
+
assert(loadedGame);
|
2167
|
+
|
2168
|
+
// Create a play selection
|
2169
|
+
const playSelection = PlaySelection.create({ value: "rock", group }, group);
|
2170
|
+
|
2171
|
+
// Assign the play selection to player1 (similar to the route logic)
|
2172
|
+
loadedGame.player1.playSelection = playSelection;
|
2173
|
+
|
2174
|
+
// Verify that the playSelection is not null and has the expected value
|
2175
|
+
expect(loadedGame.player1.playSelection).not.toBeNull();
|
2176
|
+
expect(loadedGame.player1.playSelection).toBeDefined();
|
2177
|
+
});
|
2178
|
+
|
2179
|
+
test("should assign a resolved reference and expect value to update", async () => {
|
2180
|
+
// Define the schema similar to the server-worker-http example
|
2181
|
+
const PlaySelection = co.map({
|
2182
|
+
value: z.literal(["rock", "paper", "scissors"]),
|
2183
|
+
});
|
2184
|
+
|
2185
|
+
const Player = co.map({
|
2186
|
+
account: co.account(),
|
2187
|
+
playSelection: PlaySelection,
|
2188
|
+
});
|
2189
|
+
|
2190
|
+
const Game = co.map({
|
2191
|
+
player1: Player,
|
2192
|
+
player2: Player,
|
2193
|
+
outcome: z.literal(["player1", "player2", "draw"]).optional(),
|
2194
|
+
player1Score: z.number(),
|
2195
|
+
player2Score: z.number(),
|
2196
|
+
});
|
2197
|
+
|
2198
|
+
// Create accounts for the players
|
2199
|
+
const player1Account = await createJazzTestAccount({
|
2200
|
+
creationProps: { name: "Player 1" },
|
2201
|
+
});
|
2202
|
+
const player2Account = await createJazzTestAccount({
|
2203
|
+
creationProps: { name: "Player 2" },
|
2204
|
+
});
|
2205
|
+
|
2206
|
+
// Create a game
|
2207
|
+
const game = Game.create({
|
2208
|
+
player1: Player.create({
|
2209
|
+
account: player1Account,
|
2210
|
+
playSelection: PlaySelection.create({ value: "rock" }),
|
2211
|
+
}),
|
2212
|
+
player2: Player.create({
|
2213
|
+
account: player2Account,
|
2214
|
+
playSelection: PlaySelection.create({ value: "paper" }),
|
2215
|
+
}),
|
2216
|
+
player1Score: 0,
|
2217
|
+
player2Score: 0,
|
2218
|
+
});
|
2219
|
+
|
2220
|
+
// Load the game to verify the assignment worked
|
2221
|
+
const loadedGame = await Game.load(game.id, {
|
2222
|
+
resolve: {
|
2223
|
+
player1: {
|
2224
|
+
account: true,
|
2225
|
+
playSelection: true,
|
2226
|
+
},
|
2227
|
+
player2: {
|
2228
|
+
account: true,
|
2229
|
+
playSelection: true,
|
2230
|
+
},
|
2231
|
+
},
|
2232
|
+
});
|
2233
|
+
|
2234
|
+
assert(loadedGame);
|
2235
|
+
|
2236
|
+
// Create a play selection
|
2237
|
+
const playSelection = PlaySelection.create({ value: "scissors" });
|
2238
|
+
|
2239
|
+
// Assign the play selection to player1 (similar to the route logic)
|
2240
|
+
loadedGame.player1.playSelection = playSelection;
|
2241
|
+
|
2242
|
+
// Verify that the playSelection is not null and has the expected value
|
2243
|
+
expect(loadedGame.player1.playSelection.id).toBe(playSelection.id);
|
2244
|
+
expect(loadedGame.player1.playSelection.value).toEqual("scissors");
|
2245
|
+
});
|
2246
|
+
});
|
@@ -0,0 +1,526 @@
|
|
1
|
+
import { cojsonInternals } from "cojson";
|
2
|
+
import { assert, beforeEach, describe, expect, test } from "vitest";
|
3
|
+
import { exportCoValue, importContentPieces } from "../coValues/interfaces.js";
|
4
|
+
import { Account, CoPlainText, Group, co, z } from "../exports.js";
|
5
|
+
import {
|
6
|
+
createJazzTestAccount,
|
7
|
+
createJazzTestGuest,
|
8
|
+
setupJazzTestSync,
|
9
|
+
} from "../testing.js";
|
10
|
+
|
11
|
+
cojsonInternals.CO_VALUE_LOADING_CONFIG.RETRY_DELAY = 10;
|
12
|
+
|
13
|
+
beforeEach(async () => {
|
14
|
+
await setupJazzTestSync();
|
15
|
+
await createJazzTestAccount({
|
16
|
+
isCurrentActiveAccount: true,
|
17
|
+
});
|
18
|
+
});
|
19
|
+
|
20
|
+
describe("exportCoValue", () => {
|
21
|
+
test("exports a simple CoMap", async () => {
|
22
|
+
const Person = co.map({
|
23
|
+
name: z.string(),
|
24
|
+
age: z.number(),
|
25
|
+
});
|
26
|
+
|
27
|
+
const group = Group.create();
|
28
|
+
const person = Person.create({ name: "John", age: 30 }, group);
|
29
|
+
group.addMember("everyone", "reader");
|
30
|
+
|
31
|
+
const alice = await createJazzTestAccount();
|
32
|
+
|
33
|
+
const exported = await exportCoValue(Person, person.id, {
|
34
|
+
loadAs: alice,
|
35
|
+
});
|
36
|
+
|
37
|
+
expect(exported).not.toBeNull();
|
38
|
+
expect(exported).toBeInstanceOf(Array);
|
39
|
+
expect(exported!.length).toBeGreaterThan(0);
|
40
|
+
|
41
|
+
// Verify the exported content contains the person data
|
42
|
+
const hasPersonContent = exported!.some((piece) => piece.id === person.id);
|
43
|
+
expect(hasPersonContent).toBe(true);
|
44
|
+
});
|
45
|
+
|
46
|
+
test("exports a CoMap with nested references", async () => {
|
47
|
+
const Address = co.map({
|
48
|
+
street: z.string(),
|
49
|
+
city: z.string(),
|
50
|
+
});
|
51
|
+
|
52
|
+
const Person = co.map({
|
53
|
+
name: z.string(),
|
54
|
+
address: Address,
|
55
|
+
});
|
56
|
+
|
57
|
+
const group = Group.create();
|
58
|
+
const address = Address.create(
|
59
|
+
{ street: "123 Main St", city: "New York" },
|
60
|
+
group,
|
61
|
+
);
|
62
|
+
const person = Person.create({ name: "John", address }, group);
|
63
|
+
group.addMember("everyone", "reader");
|
64
|
+
|
65
|
+
const alice = await createJazzTestAccount();
|
66
|
+
|
67
|
+
const exported = await exportCoValue(Person, person.id, {
|
68
|
+
resolve: { address: true },
|
69
|
+
loadAs: alice,
|
70
|
+
});
|
71
|
+
|
72
|
+
expect(exported).not.toBeNull();
|
73
|
+
expect(exported).toBeInstanceOf(Array);
|
74
|
+
expect(exported!.length).toBeGreaterThan(0);
|
75
|
+
|
76
|
+
// Verify both person and address content are exported
|
77
|
+
const personContent = exported!.filter((piece) => piece.id === person.id);
|
78
|
+
const addressContent = exported!.filter((piece) => piece.id === address.id);
|
79
|
+
|
80
|
+
expect(personContent.length).toBeGreaterThan(0);
|
81
|
+
expect(addressContent.length).toBeGreaterThan(0);
|
82
|
+
});
|
83
|
+
|
84
|
+
test("exports a CoList", async () => {
|
85
|
+
const TodoList = co.list(z.string());
|
86
|
+
|
87
|
+
const group = Group.create();
|
88
|
+
const todos = TodoList.create([], group);
|
89
|
+
todos.push("Buy groceries");
|
90
|
+
todos.push("Walk the dog");
|
91
|
+
group.addMember("everyone", "reader");
|
92
|
+
|
93
|
+
const alice = await createJazzTestAccount();
|
94
|
+
|
95
|
+
const exported = await exportCoValue(TodoList, todos.id, {
|
96
|
+
loadAs: alice,
|
97
|
+
});
|
98
|
+
|
99
|
+
expect(exported).not.toBeNull();
|
100
|
+
expect(exported).toBeInstanceOf(Array);
|
101
|
+
expect(exported!.length).toBeGreaterThan(0);
|
102
|
+
|
103
|
+
const hasTodoContent = exported!.some((piece) => piece.id === todos.id);
|
104
|
+
expect(hasTodoContent).toBe(true);
|
105
|
+
});
|
106
|
+
|
107
|
+
test("exports a CoStream", async () => {
|
108
|
+
const ChatStream = co.feed(z.string());
|
109
|
+
|
110
|
+
const group = Group.create();
|
111
|
+
const chat = ChatStream.create([], group);
|
112
|
+
chat.push("Hello");
|
113
|
+
chat.push("World");
|
114
|
+
group.addMember("everyone", "reader");
|
115
|
+
|
116
|
+
const alice = await createJazzTestAccount();
|
117
|
+
|
118
|
+
const exported = await exportCoValue(ChatStream, chat.id, {
|
119
|
+
loadAs: alice,
|
120
|
+
});
|
121
|
+
|
122
|
+
expect(exported).not.toBeNull();
|
123
|
+
expect(exported).toBeInstanceOf(Array);
|
124
|
+
expect(exported!.length).toBeGreaterThan(0);
|
125
|
+
|
126
|
+
const hasChatContent = exported!.some((piece) => piece.id === chat.id);
|
127
|
+
expect(hasChatContent).toBe(true);
|
128
|
+
});
|
129
|
+
|
130
|
+
test("returns null for unauthorized CoValue", async () => {
|
131
|
+
const Person = co.map({
|
132
|
+
name: z.string(),
|
133
|
+
});
|
134
|
+
|
135
|
+
const group = Group.create();
|
136
|
+
const person = Person.create({ name: "John" }, group);
|
137
|
+
// Don't add any members, so it's private
|
138
|
+
|
139
|
+
const alice = await createJazzTestAccount();
|
140
|
+
|
141
|
+
const exported = await exportCoValue(Person, person.id, {
|
142
|
+
loadAs: alice,
|
143
|
+
});
|
144
|
+
|
145
|
+
expect(exported).toBeNull();
|
146
|
+
});
|
147
|
+
|
148
|
+
test("exports with custom resolve options", async () => {
|
149
|
+
const Address = co.map({
|
150
|
+
street: z.string(),
|
151
|
+
city: z.string(),
|
152
|
+
});
|
153
|
+
|
154
|
+
const Person = co.map({
|
155
|
+
name: z.string(),
|
156
|
+
address: Address,
|
157
|
+
});
|
158
|
+
|
159
|
+
const group = Group.create();
|
160
|
+
const address = Address.create(
|
161
|
+
{ street: "123 Main St", city: "New York" },
|
162
|
+
group,
|
163
|
+
);
|
164
|
+
const person = Person.create({ name: "John", address }, group);
|
165
|
+
group.addMember("everyone", "reader");
|
166
|
+
|
167
|
+
const alice = await createJazzTestAccount();
|
168
|
+
|
169
|
+
// Export without resolving nested references
|
170
|
+
const exportedWithoutResolve = await exportCoValue(Person, person.id, {
|
171
|
+
resolve: { address: false },
|
172
|
+
loadAs: alice,
|
173
|
+
});
|
174
|
+
|
175
|
+
// Export with resolving nested references
|
176
|
+
const exportedWithResolve = await exportCoValue(Person, person.id, {
|
177
|
+
resolve: { address: true },
|
178
|
+
loadAs: alice,
|
179
|
+
});
|
180
|
+
|
181
|
+
expect(exportedWithoutResolve).not.toBeNull();
|
182
|
+
expect(exportedWithResolve).not.toBeNull();
|
183
|
+
|
184
|
+
// The version with resolve should have more content pieces
|
185
|
+
expect(exportedWithResolve!.length).toBeGreaterThanOrEqual(
|
186
|
+
exportedWithoutResolve!.length,
|
187
|
+
);
|
188
|
+
});
|
189
|
+
|
190
|
+
test("exports should handle errors on child covalues gracefully", async () => {
|
191
|
+
const Address = co.map({
|
192
|
+
street: co.plainText(),
|
193
|
+
city: co.plainText(),
|
194
|
+
});
|
195
|
+
|
196
|
+
const Person = co.map({
|
197
|
+
name: z.string(),
|
198
|
+
address: Address,
|
199
|
+
});
|
200
|
+
|
201
|
+
const group = Group.create();
|
202
|
+
const address = Address.create(
|
203
|
+
{
|
204
|
+
street: CoPlainText.create("123 Main St"),
|
205
|
+
city: CoPlainText.create("New York"),
|
206
|
+
},
|
207
|
+
group,
|
208
|
+
);
|
209
|
+
const person = Person.create({ name: "John", address }, group);
|
210
|
+
|
211
|
+
// Only add the person to the group, not the address
|
212
|
+
// This makes the address unauthorized for other accounts
|
213
|
+
group.addMember("everyone", "reader");
|
214
|
+
|
215
|
+
const alice = await createJazzTestAccount();
|
216
|
+
|
217
|
+
// Export from alice's perspective with resolve: true
|
218
|
+
// This should attempt to resolve the address but handle the error gracefully
|
219
|
+
const exported = await exportCoValue(Person, person.id, {
|
220
|
+
resolve: { address: { street: true, city: true } },
|
221
|
+
loadAs: alice,
|
222
|
+
bestEffortResolution: true,
|
223
|
+
});
|
224
|
+
|
225
|
+
assert(exported);
|
226
|
+
|
227
|
+
// Verify the person content is exported
|
228
|
+
const personContent = exported.filter((piece) => piece.id === person.id);
|
229
|
+
expect(personContent.length).toBeGreaterThan(0);
|
230
|
+
|
231
|
+
const addressContent = exported.filter((piece) => piece.id === address.id);
|
232
|
+
expect(addressContent.length).toBeGreaterThan(0);
|
233
|
+
|
234
|
+
const streetContent = exported.filter(
|
235
|
+
(piece) => piece.id === address.street.id,
|
236
|
+
);
|
237
|
+
expect(streetContent).toHaveLength(0);
|
238
|
+
|
239
|
+
const cityContent = exported.filter(
|
240
|
+
(piece) => piece.id === address.city.id,
|
241
|
+
);
|
242
|
+
expect(cityContent).toHaveLength(0);
|
243
|
+
});
|
244
|
+
});
|
245
|
+
|
246
|
+
describe("importContentPieces", () => {
|
247
|
+
test("imports content pieces successfully", async () => {
|
248
|
+
const Person = co.map({
|
249
|
+
name: z.string(),
|
250
|
+
age: z.number(),
|
251
|
+
});
|
252
|
+
|
253
|
+
const group = Group.create();
|
254
|
+
const person = Person.create({ name: "John", age: 30 }, group);
|
255
|
+
group.addMember("everyone", "reader");
|
256
|
+
|
257
|
+
const alice = await createJazzTestAccount();
|
258
|
+
const bob = await createJazzTestAccount();
|
259
|
+
|
260
|
+
bob._raw.core.node.syncManager.getPeers().forEach((peer) => {
|
261
|
+
peer.gracefulShutdown();
|
262
|
+
});
|
263
|
+
|
264
|
+
// Export from alice's perspective
|
265
|
+
const exported = await exportCoValue(Person, person.id, {
|
266
|
+
loadAs: alice,
|
267
|
+
});
|
268
|
+
|
269
|
+
expect(exported).not.toBeNull();
|
270
|
+
|
271
|
+
// Import to bob's node
|
272
|
+
importContentPieces(exported!, bob);
|
273
|
+
|
274
|
+
// Verify bob can now access the person
|
275
|
+
const importedPerson = await Person.load(person.id, { loadAs: bob });
|
276
|
+
expect(importedPerson).not.toBeNull();
|
277
|
+
expect(importedPerson?.name).toBe("John");
|
278
|
+
expect(importedPerson?.age).toBe(30);
|
279
|
+
});
|
280
|
+
|
281
|
+
test("imports content pieces with nested references", async () => {
|
282
|
+
const Address = co.map({
|
283
|
+
street: z.string(),
|
284
|
+
city: z.string(),
|
285
|
+
});
|
286
|
+
|
287
|
+
const Person = co.map({
|
288
|
+
name: z.string(),
|
289
|
+
address: Address,
|
290
|
+
});
|
291
|
+
|
292
|
+
const group = Group.create();
|
293
|
+
const address = Address.create(
|
294
|
+
{ street: "123 Main St", city: "New York" },
|
295
|
+
group,
|
296
|
+
);
|
297
|
+
const person = Person.create({ name: "John", address }, group);
|
298
|
+
group.addMember("everyone", "reader");
|
299
|
+
|
300
|
+
const alice = await createJazzTestAccount();
|
301
|
+
const bob = await createJazzTestAccount();
|
302
|
+
|
303
|
+
bob._raw.core.node.syncManager.getPeers().forEach((peer) => {
|
304
|
+
peer.gracefulShutdown();
|
305
|
+
});
|
306
|
+
|
307
|
+
// Export with resolved references
|
308
|
+
const exported = await exportCoValue(Person, person.id, {
|
309
|
+
resolve: { address: true },
|
310
|
+
loadAs: alice,
|
311
|
+
});
|
312
|
+
|
313
|
+
expect(exported).not.toBeNull();
|
314
|
+
|
315
|
+
// Import to bob's node
|
316
|
+
importContentPieces(exported!, bob);
|
317
|
+
|
318
|
+
// Verify bob can access both person and address
|
319
|
+
const importedPerson = await Person.load(person.id, {
|
320
|
+
resolve: { address: true },
|
321
|
+
loadAs: bob,
|
322
|
+
});
|
323
|
+
|
324
|
+
expect(importedPerson).not.toBeNull();
|
325
|
+
expect(importedPerson?.name).toBe("John");
|
326
|
+
expect(importedPerson?.address).not.toBeNull();
|
327
|
+
expect(importedPerson?.address.street).toBe("123 Main St");
|
328
|
+
expect(importedPerson?.address.city).toBe("New York");
|
329
|
+
});
|
330
|
+
|
331
|
+
test("imports content pieces to anonymous agent", async () => {
|
332
|
+
const Person = co.map({
|
333
|
+
name: z.string(),
|
334
|
+
});
|
335
|
+
|
336
|
+
const group = Group.create();
|
337
|
+
const person = Person.create({ name: "John" }, group);
|
338
|
+
group.addMember("everyone", "reader");
|
339
|
+
|
340
|
+
const alice = await createJazzTestAccount();
|
341
|
+
const { guest } = await createJazzTestGuest();
|
342
|
+
|
343
|
+
guest.node.syncManager.getPeers().forEach((peer) => {
|
344
|
+
peer.gracefulShutdown();
|
345
|
+
});
|
346
|
+
|
347
|
+
// Export from alice's perspective
|
348
|
+
const exported = await exportCoValue(Person, person.id, {
|
349
|
+
loadAs: alice,
|
350
|
+
});
|
351
|
+
|
352
|
+
expect(exported).not.toBeNull();
|
353
|
+
|
354
|
+
// Import to anonymous agent
|
355
|
+
importContentPieces(exported!, guest);
|
356
|
+
|
357
|
+
// Verify anonymous agent can access the person
|
358
|
+
const importedPerson = await Person.load(person.id, { loadAs: guest });
|
359
|
+
expect(importedPerson).not.toBeNull();
|
360
|
+
expect(importedPerson?.name).toBe("John");
|
361
|
+
});
|
362
|
+
|
363
|
+
test("imports content pieces without specifying loadAs (uses current account)", async () => {
|
364
|
+
const Person = co.map({
|
365
|
+
name: z.string(),
|
366
|
+
});
|
367
|
+
|
368
|
+
const group = Group.create();
|
369
|
+
const person = Person.create({ name: "John" }, group);
|
370
|
+
group.addMember("everyone", "reader");
|
371
|
+
|
372
|
+
const alice = await createJazzTestAccount();
|
373
|
+
const bob = await createJazzTestAccount({
|
374
|
+
isCurrentActiveAccount: true,
|
375
|
+
});
|
376
|
+
|
377
|
+
bob._raw.core.node.syncManager.getPeers().forEach((peer) => {
|
378
|
+
peer.gracefulShutdown();
|
379
|
+
});
|
380
|
+
|
381
|
+
// Export from alice's perspective
|
382
|
+
const exported = await exportCoValue(Person, person.id, {
|
383
|
+
loadAs: alice,
|
384
|
+
});
|
385
|
+
|
386
|
+
expect(exported).not.toBeNull();
|
387
|
+
|
388
|
+
// Import without specifying loadAs (should use current account)
|
389
|
+
importContentPieces(exported!);
|
390
|
+
|
391
|
+
// Verify bob can access the person
|
392
|
+
const importedPerson = await Person.load(person.id, { loadAs: bob });
|
393
|
+
expect(importedPerson).not.toBeNull();
|
394
|
+
expect(importedPerson?.name).toBe("John");
|
395
|
+
});
|
396
|
+
|
397
|
+
test("handles empty content pieces array", async () => {
|
398
|
+
const alice = await createJazzTestAccount();
|
399
|
+
|
400
|
+
// Should not throw when importing empty array
|
401
|
+
expect(() => {
|
402
|
+
importContentPieces([], alice);
|
403
|
+
}).not.toThrow();
|
404
|
+
});
|
405
|
+
|
406
|
+
test("handles duplicate content pieces", async () => {
|
407
|
+
const Person = co.map({
|
408
|
+
name: z.string(),
|
409
|
+
});
|
410
|
+
|
411
|
+
const group = Group.create();
|
412
|
+
const person = Person.create({ name: "John" }, group);
|
413
|
+
group.addMember("everyone", "reader");
|
414
|
+
|
415
|
+
const alice = await createJazzTestAccount();
|
416
|
+
const bob = await createJazzTestAccount();
|
417
|
+
|
418
|
+
bob._raw.core.node.syncManager.getPeers().forEach((peer) => {
|
419
|
+
peer.gracefulShutdown();
|
420
|
+
});
|
421
|
+
|
422
|
+
// Export from alice's perspective
|
423
|
+
const exported = await exportCoValue(Person, person.id, {
|
424
|
+
loadAs: alice,
|
425
|
+
});
|
426
|
+
|
427
|
+
expect(exported).not.toBeNull();
|
428
|
+
|
429
|
+
// Import the same content pieces twice
|
430
|
+
importContentPieces(exported!, bob);
|
431
|
+
importContentPieces(exported!, bob);
|
432
|
+
|
433
|
+
// Should still work correctly
|
434
|
+
const importedPerson = await Person.load(person.id, { loadAs: bob });
|
435
|
+
expect(importedPerson).not.toBeNull();
|
436
|
+
expect(importedPerson?.name).toBe("John");
|
437
|
+
});
|
438
|
+
|
439
|
+
test("imports content pieces with complex nested structure", async () => {
|
440
|
+
const Comment = co.map({
|
441
|
+
text: z.string(),
|
442
|
+
author: z.string(),
|
443
|
+
});
|
444
|
+
|
445
|
+
const Post = co.map({
|
446
|
+
title: z.string(),
|
447
|
+
content: z.string(),
|
448
|
+
comments: co.list(Comment),
|
449
|
+
});
|
450
|
+
|
451
|
+
const Blog = co.map({
|
452
|
+
name: z.string(),
|
453
|
+
posts: co.list(Post),
|
454
|
+
});
|
455
|
+
|
456
|
+
const group = Group.create();
|
457
|
+
const comment1 = Comment.create(
|
458
|
+
{ text: "Great post!", author: "Alice" },
|
459
|
+
group,
|
460
|
+
);
|
461
|
+
const comment2 = Comment.create({ text: "Thanks!", author: "Bob" }, group);
|
462
|
+
|
463
|
+
const post = Post.create(
|
464
|
+
{
|
465
|
+
title: "My First Post",
|
466
|
+
content: "Hello World",
|
467
|
+
comments: Post.def.shape.comments.create([comment1, comment2], group),
|
468
|
+
},
|
469
|
+
group,
|
470
|
+
);
|
471
|
+
|
472
|
+
const blog = Blog.create(
|
473
|
+
{ name: "My Blog", posts: Blog.def.shape.posts.create([post], group) },
|
474
|
+
group,
|
475
|
+
);
|
476
|
+
|
477
|
+
group.addMember("everyone", "reader");
|
478
|
+
|
479
|
+
const alice = await createJazzTestAccount();
|
480
|
+
const bob = await createJazzTestAccount();
|
481
|
+
|
482
|
+
bob._raw.core.node.syncManager.getPeers().forEach((peer) => {
|
483
|
+
peer.gracefulShutdown();
|
484
|
+
});
|
485
|
+
|
486
|
+
// Export with all nested references resolved
|
487
|
+
const exported = await exportCoValue(Blog, blog.id, {
|
488
|
+
resolve: {
|
489
|
+
posts: {
|
490
|
+
$each: {
|
491
|
+
comments: true,
|
492
|
+
},
|
493
|
+
},
|
494
|
+
},
|
495
|
+
loadAs: alice,
|
496
|
+
});
|
497
|
+
|
498
|
+
expect(exported).not.toBeNull();
|
499
|
+
|
500
|
+
// Import to bob's node
|
501
|
+
importContentPieces(exported!, bob);
|
502
|
+
|
503
|
+
// Verify bob can access the entire structure
|
504
|
+
const importedBlog = await Blog.load(blog.id, {
|
505
|
+
resolve: {
|
506
|
+
posts: {
|
507
|
+
$each: {
|
508
|
+
comments: true,
|
509
|
+
},
|
510
|
+
},
|
511
|
+
},
|
512
|
+
loadAs: bob,
|
513
|
+
});
|
514
|
+
|
515
|
+
expect(importedBlog).not.toBeNull();
|
516
|
+
expect(importedBlog?.name).toBe("My Blog");
|
517
|
+
expect(importedBlog?.posts.length).toBe(1);
|
518
|
+
expect(importedBlog?.posts[0]?.title).toBe("My First Post");
|
519
|
+
expect(importedBlog?.posts[0]?.content).toBe("Hello World");
|
520
|
+
expect(importedBlog?.posts[0]?.comments.length).toBe(2);
|
521
|
+
expect(importedBlog?.posts[0]?.comments[0]?.text).toBe("Great post!");
|
522
|
+
expect(importedBlog?.posts[0]?.comments[0]?.author).toBe("Alice");
|
523
|
+
expect(importedBlog?.posts[0]?.comments[1]?.text).toBe("Thanks!");
|
524
|
+
expect(importedBlog?.posts[0]?.comments[1]?.author).toBe("Bob");
|
525
|
+
});
|
526
|
+
});
|