jazz-tools 0.7.35-unique.2 → 0.8.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 (46) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/.turbo/turbo-lint.log +1 -1
  3. package/.turbo/turbo-test.log +44 -57
  4. package/CHANGELOG.md +27 -7
  5. package/dist/coValues/account.js +8 -2
  6. package/dist/coValues/account.js.map +1 -1
  7. package/dist/coValues/coList.js +5 -2
  8. package/dist/coValues/coList.js.map +1 -1
  9. package/dist/coValues/coMap.js +16 -9
  10. package/dist/coValues/coMap.js.map +1 -1
  11. package/dist/coValues/coStream.js +30 -3
  12. package/dist/coValues/coStream.js.map +1 -1
  13. package/dist/coValues/interfaces.js +5 -2
  14. package/dist/coValues/interfaces.js.map +1 -1
  15. package/dist/implementation/createContext.js +55 -5
  16. package/dist/implementation/createContext.js.map +1 -1
  17. package/dist/implementation/devtoolsFormatters.js +1 -0
  18. package/dist/implementation/devtoolsFormatters.js.map +1 -1
  19. package/dist/implementation/refs.js +8 -2
  20. package/dist/implementation/refs.js.map +1 -1
  21. package/dist/implementation/schema.js.map +1 -1
  22. package/dist/implementation/subscriptionScope.js +4 -3
  23. package/dist/implementation/subscriptionScope.js.map +1 -1
  24. package/dist/index.js +1 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/tests/coMap.test.js +92 -1
  27. package/dist/tests/coMap.test.js.map +1 -1
  28. package/dist/tests/coStream.test.js +37 -0
  29. package/dist/tests/coStream.test.js.map +1 -1
  30. package/dist/tests/deepLoading.test.js +7 -7
  31. package/dist/tests/deepLoading.test.js.map +1 -1
  32. package/package.json +4 -4
  33. package/src/coValues/account.ts +9 -3
  34. package/src/coValues/coList.ts +5 -1
  35. package/src/coValues/coMap.ts +56 -17
  36. package/src/coValues/coStream.ts +38 -4
  37. package/src/coValues/interfaces.ts +8 -4
  38. package/src/implementation/createContext.ts +117 -11
  39. package/src/implementation/devtoolsFormatters.ts +1 -0
  40. package/src/implementation/refs.ts +13 -6
  41. package/src/implementation/schema.ts +1 -0
  42. package/src/implementation/subscriptionScope.ts +28 -25
  43. package/src/index.ts +3 -0
  44. package/src/tests/coMap.test.ts +139 -1
  45. package/src/tests/coStream.test.ts +53 -0
  46. package/src/tests/deepLoading.test.ts +7 -7
@@ -5,6 +5,7 @@ import type {
5
5
  ID,
6
6
  CoValueClass,
7
7
  CoValueFromRaw,
8
+ AnonymousJazzAgent,
8
9
  } from "../internal.js";
9
10
 
10
11
  export const subscriptionsScopes = new WeakMap<
@@ -17,7 +18,7 @@ const TRACE_INVALIDATIONS = false;
17
18
 
18
19
  export class SubscriptionScope<Root extends CoValue> {
19
20
  scopeID: string = `scope-${Math.random().toString(36).slice(2)}`;
20
- subscriber: Account;
21
+ subscriber: Account | AnonymousJazzAgent;
21
22
  entries = new Map<
22
23
  ID<CoValue>,
23
24
  | { state: "loading"; immediatelyUnsub?: boolean }
@@ -89,32 +90,34 @@ export class SubscriptionScope<Root extends CoValue> {
89
90
  immediatelyUnsub: false,
90
91
  } as const;
91
92
  this.entries.set(accessedOrSetId, loadingEntry);
92
- void this.subscriber._raw.core.node
93
- .loadCoValueCore(accessedOrSetId)
94
- .then((core) => {
95
- if (
96
- loadingEntry.state === "loading" &&
97
- loadingEntry.immediatelyUnsub
98
- ) {
99
- return;
100
- }
101
- if (core !== "unavailable") {
102
- const entry = {
103
- state: "loaded" as const,
104
- rawUnsub: () => {}, // placeholder
105
- };
106
- this.entries.set(accessedOrSetId, entry);
93
+ const node =
94
+ this.subscriber._type === "Account"
95
+ ? this.subscriber._raw.core.node
96
+ : this.subscriber.node;
97
+ void node.loadCoValueCore(accessedOrSetId).then((core) => {
98
+ if (
99
+ loadingEntry.state === "loading" &&
100
+ loadingEntry.immediatelyUnsub
101
+ ) {
102
+ return;
103
+ }
104
+ if (core !== "unavailable") {
105
+ const entry = {
106
+ state: "loaded" as const,
107
+ rawUnsub: () => {}, // placeholder
108
+ };
109
+ this.entries.set(accessedOrSetId, entry);
107
110
 
108
- const rawUnsub = core.subscribe((rawUpdate) => {
109
- // console.log("ref update", this.scopeID, accessedOrSetId, JSON.stringify(rawUpdate))
110
- if (!rawUpdate) return;
111
- this.invalidate(accessedOrSetId);
112
- this.scheduleUpdate();
113
- });
111
+ const rawUnsub = core.subscribe((rawUpdate) => {
112
+ // console.log("ref update", this.scopeID, accessedOrSetId, JSON.stringify(rawUpdate))
113
+ if (!rawUpdate) return;
114
+ this.invalidate(accessedOrSetId);
115
+ this.scheduleUpdate();
116
+ });
114
117
 
115
- entry.rawUnsub = rawUnsub;
116
- }
117
- });
118
+ entry.rawUnsub = rawUnsub;
119
+ }
120
+ });
118
121
  }
119
122
  }
120
123
 
package/src/index.ts CHANGED
@@ -34,4 +34,7 @@ export {
34
34
  type AuthResult,
35
35
  createJazzContext,
36
36
  fixedCredentialsAuth,
37
+ ephemeralCredentialsAuth,
38
+ AnonymousJazzAgent,
39
+ createAnonymousJazzContext,
37
40
  } from "./internal.js";
@@ -1,4 +1,4 @@
1
- import { expect, describe, test } from "vitest";
1
+ import { expect, describe, test, expectTypeOf } from "vitest";
2
2
  import { connectedPeers } from "cojson/src/streamUtils.js";
3
3
  import {
4
4
  Account,
@@ -592,6 +592,7 @@ describe("CoMap applyDiff", async () => {
592
592
  birthday = co.encoded(Encoders.Date);
593
593
  nested = co.ref(NestedMap);
594
594
  optionalField = co.optional.string;
595
+ optionalNested = co.optional.ref(NestedMap);
595
596
  }
596
597
 
597
598
  class NestedMap extends CoMap {
@@ -737,6 +738,143 @@ describe("CoMap applyDiff", async () => {
737
738
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
738
739
  expect((map as any).invalidField).toBeUndefined();
739
740
  });
741
+
742
+ test("applyDiff with optional reference set to null", () => {
743
+ const map = TestMap.create(
744
+ {
745
+ name: "Jack",
746
+ age: 50,
747
+ isActive: true,
748
+ birthday: new Date("1970-01-01"),
749
+ nested: NestedMap.create({ value: "original" }, { owner: me }),
750
+ optionalNested: NestedMap.create(
751
+ { value: "optional" },
752
+ { owner: me },
753
+ ),
754
+ },
755
+ { owner: me },
756
+ );
757
+
758
+ const newValues = {
759
+ optionalNested: null,
760
+ };
761
+
762
+ map.applyDiff(newValues);
763
+
764
+ expect(map.optionalNested).toBeNull();
765
+ });
766
+
767
+ test("applyDiff with required reference set to null should throw", () => {
768
+ const map = TestMap.create(
769
+ {
770
+ name: "Kate",
771
+ age: 55,
772
+ isActive: true,
773
+ birthday: new Date("1965-01-01"),
774
+ nested: NestedMap.create({ value: "original" }, { owner: me }),
775
+ },
776
+ { owner: me },
777
+ );
778
+
779
+ const newValues = {
780
+ nested: null,
781
+ };
782
+
783
+ // @ts-expect-error testing invalid usage
784
+ expect(() => map.applyDiff(newValues)).toThrowError(
785
+ "Cannot set required reference nested to null",
786
+ );
787
+ });
788
+ });
789
+
790
+ describe("CoMap Typescript validation", async () => {
791
+ const me = await Account.create({
792
+ creationProps: { name: "Hermes Puggington" },
793
+ crypto: Crypto,
794
+ });
795
+
796
+ test("Is not ok to pass null into a required ref", () => {
797
+ class TestMap extends CoMap {
798
+ required = co.ref(NestedMap);
799
+ optional = co.optional.ref(NestedMap);
800
+ }
801
+
802
+ class NestedMap extends CoMap {
803
+ value = co.string;
804
+ }
805
+
806
+ expectTypeOf<typeof TestMap.create<TestMap>>().toBeCallableWith(
807
+ {
808
+ optional: NestedMap.create({ value: "" }, { owner: me }),
809
+ // @ts-expect-error null can't be passed to a non-optional field
810
+ required: null,
811
+ },
812
+ { owner: me },
813
+ );
814
+ });
815
+
816
+ test("Is not ok if a required ref is omitted", () => {
817
+ class TestMap extends CoMap {
818
+ required = co.ref(NestedMap);
819
+ optional = co.ref(NestedMap, { optional: true });
820
+ }
821
+
822
+ class NestedMap extends CoMap {
823
+ value = co.string;
824
+ }
825
+
826
+ expectTypeOf<typeof TestMap.create<TestMap>>().toBeCallableWith(
827
+ // @ts-expect-error non-optional fields can't be omitted
828
+ {},
829
+ { owner: me },
830
+ );
831
+ });
832
+
833
+ test("Is ok to omit optional fields", () => {
834
+ class TestMap extends CoMap {
835
+ required = co.ref(NestedMap);
836
+ optional = co.ref(NestedMap, { optional: true });
837
+ }
838
+
839
+ class NestedMap extends CoMap {
840
+ value = co.string;
841
+ }
842
+
843
+ expectTypeOf<typeof TestMap.create<TestMap>>().toBeCallableWith(
844
+ {
845
+ required: NestedMap.create({ value: "" }, { owner: me }),
846
+ },
847
+ { owner: me },
848
+ );
849
+
850
+ expectTypeOf<typeof TestMap.create<TestMap>>().toBeCallableWith(
851
+ {
852
+ required: NestedMap.create({ value: "" }, { owner: me }),
853
+ optional: null,
854
+ },
855
+ { owner: me },
856
+ );
857
+ });
858
+
859
+ test("the required refs should be nullable", () => {
860
+ class TestMap extends CoMap {
861
+ required = co.ref(NestedMap);
862
+ optional = co.ref(NestedMap, { optional: true });
863
+ }
864
+
865
+ class NestedMap extends CoMap {
866
+ value = co.string;
867
+ }
868
+
869
+ const map = TestMap.create(
870
+ {
871
+ required: NestedMap.create({ value: "" }, { owner: me }),
872
+ },
873
+ { owner: me },
874
+ );
875
+
876
+ expectTypeOf(map.required).toBeNullable();
877
+ });
740
878
  });
741
879
 
742
880
  describe("Creating and finding unique CoMaps", async () => {
@@ -443,3 +443,56 @@ describe("BinaryCoStream loading & Subscription", async () => {
443
443
  });
444
444
  });
445
445
  });
446
+
447
+ describe("BinaryCoStream.loadAsBlob", async () => {
448
+ async function setup() {
449
+ const me = await Account.create({
450
+ creationProps: { name: "Hermes Puggington" },
451
+ crypto: Crypto,
452
+ });
453
+
454
+ const stream = BinaryCoStream.create({ owner: me });
455
+
456
+ stream.start({ mimeType: "text/plain" });
457
+
458
+ return { stream, me };
459
+ }
460
+
461
+ test("resolves only when the stream is ended", async () => {
462
+ const { stream, me } = await setup();
463
+ stream.push(new Uint8Array([1]));
464
+
465
+ const promise = BinaryCoStream.loadAsBlob(stream.id, me);
466
+
467
+ await stream.ensureLoaded([]);
468
+
469
+ stream.push(new Uint8Array([2]));
470
+ stream.end();
471
+
472
+ const blob = await promise;
473
+
474
+ // The promise resolves only when the stream is ended
475
+ // so we get a blob with all the chunks
476
+ expect(blob?.size).toBe(2);
477
+ });
478
+
479
+ test("resolves with an unfinshed blob if allowUnfinished: true", async () => {
480
+ const { stream, me } = await setup();
481
+ stream.push(new Uint8Array([1]));
482
+
483
+ const promise = BinaryCoStream.loadAsBlob(stream.id, me, {
484
+ allowUnfinished: true,
485
+ });
486
+
487
+ await stream.ensureLoaded([]);
488
+
489
+ stream.push(new Uint8Array([2]));
490
+ stream.end();
491
+
492
+ const blob = await promise;
493
+
494
+ // The promise resolves before the stream is ended
495
+ // so we get a blob only with the first chunk
496
+ expect(blob?.size).toBe(1);
497
+ });
498
+ });
@@ -60,6 +60,7 @@ describe("Deep loading with depth arg", async () => {
60
60
  });
61
61
 
62
62
  test("loading a deeply nested object will wait until all required refs are loaded", async () => {
63
+ const ownership = { owner: me };
63
64
  const map = TestMap.create(
64
65
  {
65
66
  list: TestList.create(
@@ -70,19 +71,19 @@ describe("Deep loading with depth arg", async () => {
70
71
  [
71
72
  InnermostMap.create(
72
73
  { value: "hello" },
73
- { owner: me },
74
+ ownership,
74
75
  ),
75
76
  ],
76
- { owner: me },
77
+ ownership,
77
78
  ),
78
79
  },
79
- { owner: me },
80
+ ownership,
80
81
  ),
81
82
  ],
82
- { owner: me },
83
+ ownership,
83
84
  ),
84
85
  },
85
- { owner: me },
86
+ ownership,
86
87
  );
87
88
 
88
89
  const map1 = await TestMap.load(map.id, meOnSecondPeer, {});
@@ -141,8 +142,7 @@ describe("Deep loading with depth arg", async () => {
141
142
  throw new Error("map4 is undefined");
142
143
  }
143
144
  expect(map4.list[0]?.stream).not.toBe(null);
144
- // TODO: why is this actually defined?
145
- // expect(map4.list[0]?.stream?.[me.id]).toBe(undefined)
145
+ expect(map4.list[0]?.stream?.[me.id]).not.toBe(null);
146
146
  expect(map4.list[0]?.stream?.byMe?.value).toBe(null);
147
147
 
148
148
  const map5 = await TestMap.load(map.id, meOnSecondPeer, {