jazz-tools 0.10.7 → 0.10.12
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 +3 -3
- package/CHANGELOG.md +14 -0
- package/dist/{chunk-TSEO4KAO.js → chunk-RL7HVQ5Q.js} +24 -3
- package/dist/chunk-RL7HVQ5Q.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/testing.js +1 -1
- package/package.json +2 -2
- package/src/coValues/deepLoading.ts +25 -3
- package/src/coValues/interfaces.ts +7 -6
- package/src/tests/ContextManager.test.ts +0 -1
- package/src/tests/coMap.test.ts +2 -2
- package/src/tests/deepLoading.test.ts +129 -1
- package/src/tests/subscribe.test.ts +39 -0
- package/dist/chunk-TSEO4KAO.js.map +0 -1
package/dist/index.js
CHANGED
package/dist/testing.js
CHANGED
package/package.json
CHANGED
@@ -29,11 +29,33 @@ export function fulfillsDepth(depth: any, value: CoValue): boolean {
|
|
29
29
|
const map = value as unknown as {
|
30
30
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
31
31
|
[key: string]: any;
|
32
|
-
_refs: { [key: string]: Ref<CoValue> | undefined };
|
33
32
|
};
|
34
|
-
|
35
|
-
|
33
|
+
|
34
|
+
if (map._raw.get(key) === undefined) {
|
35
|
+
if (!map._schema?.[key]) {
|
36
|
+
if (map._schema?.[ItemsSym]) {
|
37
|
+
// CoMap.Record
|
38
|
+
if (map._schema[ItemsSym].optional) {
|
39
|
+
continue;
|
40
|
+
} else {
|
41
|
+
throw new Error(
|
42
|
+
`The ref ${key} requested on ${map.constructor.name} is missing`,
|
43
|
+
);
|
44
|
+
}
|
45
|
+
} else {
|
46
|
+
throw new Error(
|
47
|
+
`The ref ${key} requested on ${map.constructor.name} is not defined in the schema`,
|
48
|
+
);
|
49
|
+
}
|
50
|
+
} else if (map._schema[key].optional) {
|
51
|
+
continue;
|
52
|
+
} else {
|
53
|
+
throw new Error(
|
54
|
+
`The ref ${key} on ${map.constructor.name} is required but missing`,
|
55
|
+
);
|
56
|
+
}
|
36
57
|
}
|
58
|
+
|
37
59
|
if (!map[key]) {
|
38
60
|
return false;
|
39
61
|
}
|
@@ -157,7 +157,7 @@ export class CoValueBase implements CoValue {
|
|
157
157
|
|
158
158
|
export function loadCoValueWithoutMe<V extends CoValue, Depth>(
|
159
159
|
cls: CoValueClass<V>,
|
160
|
-
id: ID<
|
160
|
+
id: ID<CoValue>,
|
161
161
|
asOrDepth: Account | AnonymousJazzAgent | (Depth & DepthsIn<V>),
|
162
162
|
depth?: Depth & DepthsIn<V>,
|
163
163
|
) {
|
@@ -175,7 +175,7 @@ export function loadCoValueWithoutMe<V extends CoValue, Depth>(
|
|
175
175
|
|
176
176
|
export function loadCoValue<V extends CoValue, Depth>(
|
177
177
|
cls: CoValueClass<V>,
|
178
|
-
id: ID<
|
178
|
+
id: ID<CoValue>,
|
179
179
|
as: Account | AnonymousJazzAgent,
|
180
180
|
depth: Depth & DepthsIn<V>,
|
181
181
|
): Promise<DeeplyLoaded<V, Depth> | undefined> {
|
@@ -216,7 +216,7 @@ export async function ensureCoValueLoaded<V extends CoValue, Depth>(
|
|
216
216
|
|
217
217
|
export function subscribeToCoValueWithoutMe<V extends CoValue, Depth>(
|
218
218
|
cls: CoValueClass<V>,
|
219
|
-
id: ID<
|
219
|
+
id: ID<CoValue>,
|
220
220
|
asOrDepth: Account | AnonymousJazzAgent | (Depth & DepthsIn<V>),
|
221
221
|
depthOrListener:
|
222
222
|
| (Depth & DepthsIn<V>)
|
@@ -251,7 +251,7 @@ export function subscribeToCoValueWithoutMe<V extends CoValue, Depth>(
|
|
251
251
|
|
252
252
|
export function subscribeToCoValue<V extends CoValue, Depth>(
|
253
253
|
cls: CoValueClass<V>,
|
254
|
-
id: ID<
|
254
|
+
id: ID<CoValue>,
|
255
255
|
as: Account | AnonymousJazzAgent,
|
256
256
|
depth: Depth & DepthsIn<V>,
|
257
257
|
listener: (value: DeeplyLoaded<V, Depth>, unsubscribe: () => void) => void,
|
@@ -263,7 +263,7 @@ export function subscribeToCoValue<V extends CoValue, Depth>(
|
|
263
263
|
let unsubscribed = false;
|
264
264
|
let unsubscribe: (() => void) | undefined;
|
265
265
|
|
266
|
-
function subscribe(value:
|
266
|
+
function subscribe(value: CoValue | undefined) {
|
267
267
|
if (!value) {
|
268
268
|
onUnavailable && onUnavailable();
|
269
269
|
return;
|
@@ -295,6 +295,7 @@ export function subscribeToCoValue<V extends CoValue, Depth>(
|
|
295
295
|
.then((value) => subscribe(value))
|
296
296
|
.catch((e) => {
|
297
297
|
console.error("Failed to load / subscribe to CoValue", e);
|
298
|
+
onUnavailable?.();
|
298
299
|
});
|
299
300
|
}
|
300
301
|
|
@@ -312,7 +313,7 @@ export function createCoValueObservable<V extends CoValue, Depth>(options?: {
|
|
312
313
|
|
313
314
|
function subscribe(
|
314
315
|
cls: CoValueClass<V>,
|
315
|
-
id: ID<
|
316
|
+
id: ID<CoValue>,
|
316
317
|
as: Account | AnonymousJazzAgent,
|
317
318
|
depth: Depth & DepthsIn<V>,
|
318
319
|
listener: () => void,
|
package/src/tests/coMap.test.ts
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import { WasmCrypto } from "cojson/crypto/WasmCrypto";
|
2
|
-
import { describe, expect, expectTypeOf, test } from "vitest";
|
2
|
+
import { describe, expect, expectTypeOf, test, vi } from "vitest";
|
3
3
|
import { Group, randomSessionProvider } from "../exports.js";
|
4
4
|
import {
|
5
5
|
Account,
|
@@ -10,7 +10,7 @@ import {
|
|
10
10
|
createJazzContextFromExistingCredentials,
|
11
11
|
isControlledAccount,
|
12
12
|
} from "../index.js";
|
13
|
-
import { setupTwoNodes } from "./utils.js";
|
13
|
+
import { setupTwoNodes, waitFor } from "./utils.js";
|
14
14
|
|
15
15
|
const connectedPeers = cojsonInternals.connectedPeers;
|
16
16
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import { cojsonInternals } from "cojson";
|
2
2
|
import { WasmCrypto } from "cojson/crypto/WasmCrypto";
|
3
|
-
import { describe, expect, expectTypeOf, test } from "vitest";
|
3
|
+
import { describe, expect, expectTypeOf, test, vi } from "vitest";
|
4
4
|
import {
|
5
5
|
Account,
|
6
6
|
CoFeed,
|
@@ -14,6 +14,7 @@ import {
|
|
14
14
|
isControlledAccount,
|
15
15
|
} from "../index.js";
|
16
16
|
import { randomSessionProvider } from "../internal.js";
|
17
|
+
import { waitFor } from "./utils.js";
|
17
18
|
|
18
19
|
const Crypto = await WasmCrypto.create();
|
19
20
|
const { connectedPeers } = cojsonInternals;
|
@@ -303,3 +304,130 @@ test("Deep loading a record-like coMap", async () => {
|
|
303
304
|
expect(recordLoaded.key2?.list).not.toBe(null);
|
304
305
|
expect(recordLoaded.key2?.list).not.toBe(undefined);
|
305
306
|
});
|
307
|
+
|
308
|
+
test("doesn't break on Map.Record key deletion when the key is referenced in the depth", async () => {
|
309
|
+
class JazzProfile extends CoMap {
|
310
|
+
firstName = co.string;
|
311
|
+
}
|
312
|
+
|
313
|
+
class JazzySnapStore extends CoMap.Record(co.ref(JazzProfile)) {}
|
314
|
+
|
315
|
+
const me = await Account.create({
|
316
|
+
creationProps: { name: "Tester McTesterson" },
|
317
|
+
crypto: Crypto,
|
318
|
+
});
|
319
|
+
|
320
|
+
const snapStore = JazzySnapStore.create(
|
321
|
+
{
|
322
|
+
profile1: JazzProfile.create({ firstName: "John" }, { owner: me }),
|
323
|
+
profile2: JazzProfile.create({ firstName: "John" }, { owner: me }),
|
324
|
+
},
|
325
|
+
{ owner: me },
|
326
|
+
);
|
327
|
+
|
328
|
+
const spy = vi.fn();
|
329
|
+
const unsub = snapStore.subscribe({ profile1: {}, profile2: {} }, spy);
|
330
|
+
|
331
|
+
await waitFor(() => expect(spy).toHaveBeenCalled());
|
332
|
+
|
333
|
+
spy.mockClear();
|
334
|
+
delete snapStore.profile1;
|
335
|
+
|
336
|
+
expect(Object.keys(snapStore)).toEqual(["profile2"]);
|
337
|
+
|
338
|
+
unsub();
|
339
|
+
|
340
|
+
await expect(
|
341
|
+
snapStore.ensureLoaded({
|
342
|
+
profile1: {},
|
343
|
+
}),
|
344
|
+
).rejects.toThrow("Failed to deeply load CoValue " + snapStore.id);
|
345
|
+
});
|
346
|
+
|
347
|
+
test("throw when calling ensureLoaded on a ref that's required but missing", async () => {
|
348
|
+
class JazzProfile extends CoMap {
|
349
|
+
firstName = co.string;
|
350
|
+
}
|
351
|
+
|
352
|
+
class JazzRoot extends CoMap {
|
353
|
+
profile = co.ref(JazzProfile);
|
354
|
+
}
|
355
|
+
|
356
|
+
const me = await Account.create({
|
357
|
+
creationProps: { name: "Tester McTesterson" },
|
358
|
+
crypto: Crypto,
|
359
|
+
});
|
360
|
+
|
361
|
+
const root = JazzRoot.create(
|
362
|
+
// @ts-expect-error missing required ref
|
363
|
+
{},
|
364
|
+
{ owner: me },
|
365
|
+
);
|
366
|
+
|
367
|
+
await expect(
|
368
|
+
root.ensureLoaded({
|
369
|
+
profile: {},
|
370
|
+
}),
|
371
|
+
).rejects.toThrow("Failed to deeply load CoValue " + root.id);
|
372
|
+
});
|
373
|
+
|
374
|
+
test("throw when calling ensureLoaded on a ref that is not defined in the schema", async () => {
|
375
|
+
class JazzRoot extends CoMap {}
|
376
|
+
|
377
|
+
const me = await Account.create({
|
378
|
+
creationProps: { name: "Tester McTesterson" },
|
379
|
+
crypto: Crypto,
|
380
|
+
});
|
381
|
+
|
382
|
+
const root = JazzRoot.create({}, { owner: me });
|
383
|
+
|
384
|
+
await expect(
|
385
|
+
root.ensureLoaded({
|
386
|
+
profile: {},
|
387
|
+
}),
|
388
|
+
).rejects.toThrow("Failed to deeply load CoValue " + root.id);
|
389
|
+
});
|
390
|
+
|
391
|
+
test("should not throw when calling ensureLoaded a record with a deleted ref", async () => {
|
392
|
+
class JazzProfile extends CoMap {
|
393
|
+
firstName = co.string;
|
394
|
+
}
|
395
|
+
|
396
|
+
class JazzySnapStore extends CoMap.Record(co.ref(JazzProfile)) {}
|
397
|
+
|
398
|
+
const me = await Account.create({
|
399
|
+
creationProps: { name: "Tester McTesterson" },
|
400
|
+
crypto: Crypto,
|
401
|
+
});
|
402
|
+
|
403
|
+
const root = JazzySnapStore.create(
|
404
|
+
{
|
405
|
+
profile: JazzProfile.create({ firstName: "John" }, me),
|
406
|
+
},
|
407
|
+
me,
|
408
|
+
);
|
409
|
+
|
410
|
+
let value: any;
|
411
|
+
let unsub = root.subscribe([{}], (v) => {
|
412
|
+
value = v;
|
413
|
+
});
|
414
|
+
|
415
|
+
await waitFor(() => expect(value.profile).toBeDefined());
|
416
|
+
|
417
|
+
delete root.profile;
|
418
|
+
|
419
|
+
await waitFor(() => expect(value.profile).toBeUndefined());
|
420
|
+
|
421
|
+
unsub();
|
422
|
+
|
423
|
+
value = undefined;
|
424
|
+
unsub = root.subscribe([{}], (v) => {
|
425
|
+
value = v;
|
426
|
+
});
|
427
|
+
|
428
|
+
await waitFor(() => expect(value).toBeDefined());
|
429
|
+
|
430
|
+
expect(value.profile).toBeUndefined();
|
431
|
+
|
432
|
+
unsub();
|
433
|
+
});
|
@@ -150,6 +150,45 @@ describe("subscribeToCoValue", () => {
|
|
150
150
|
);
|
151
151
|
});
|
152
152
|
|
153
|
+
it("shouldn't fire updates after unsubscribing", async () => {
|
154
|
+
const { me, meOnSecondPeer } = await setupAccount();
|
155
|
+
|
156
|
+
const chatRoom = createChatRoom(me, "General");
|
157
|
+
const updateFn = vi.fn();
|
158
|
+
|
159
|
+
const { messages } = await chatRoom.ensureLoaded({ messages: [{}] });
|
160
|
+
|
161
|
+
messages.push(createMessage(me, "Hello"));
|
162
|
+
|
163
|
+
const unsubscribe = subscribeToCoValue(
|
164
|
+
ChatRoom,
|
165
|
+
chatRoom.id,
|
166
|
+
meOnSecondPeer,
|
167
|
+
{
|
168
|
+
messages: [{}],
|
169
|
+
},
|
170
|
+
updateFn,
|
171
|
+
);
|
172
|
+
|
173
|
+
await waitFor(() => {
|
174
|
+
expect(updateFn).toHaveBeenCalled();
|
175
|
+
});
|
176
|
+
|
177
|
+
unsubscribe();
|
178
|
+
chatRoom.name = "Lounge";
|
179
|
+
messages.push(createMessage(me, "Hello 2"));
|
180
|
+
|
181
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
182
|
+
|
183
|
+
expect(updateFn).toHaveBeenCalledTimes(1);
|
184
|
+
expect(updateFn).toHaveBeenCalledWith(
|
185
|
+
expect.objectContaining({
|
186
|
+
id: chatRoom.id,
|
187
|
+
}),
|
188
|
+
expect.any(Function),
|
189
|
+
);
|
190
|
+
});
|
191
|
+
|
153
192
|
it("should fire updates when a ref entity is updates", async () => {
|
154
193
|
const { me, meOnSecondPeer } = await setupAccount();
|
155
194
|
|