jazz-tools 0.7.35-unique.2 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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, {