jazz-tools 0.7.23 → 0.7.25

Sign up to get free protection for your applications and to get access to all the features.
@@ -17,11 +17,9 @@ import type {
17
17
  ID,
18
18
  IfCo,
19
19
  UnCo,
20
- AccountCtx,
21
20
  CoValueClass,
22
21
  DeeplyLoaded,
23
22
  DepthsIn,
24
- UnavailableError,
25
23
  } from "../internal.js";
26
24
  import {
27
25
  ItemsSym,
@@ -33,14 +31,11 @@ import {
33
31
  SchemaInit,
34
32
  isRefEncoded,
35
33
  loadCoValue,
36
- loadCoValueEf,
37
34
  subscribeToCoValue,
38
- subscribeToCoValueEf,
39
35
  ensureCoValueLoaded,
40
36
  subscribeToExistingCoValue,
41
37
  } from "../internal.js";
42
38
  import { encodeSync, decodeSync } from "@effect/schema/Schema";
43
- import { Effect, Stream } from "effect";
44
39
 
45
40
  export type CoStreamEntry<Item> = SingleCoStreamEntry<Item> & {
46
41
  all: IterableIterator<SingleCoStreamEntry<Item>>;
@@ -202,15 +197,6 @@ export class CoStream<Item = any> extends CoValueBase implements CoValue {
202
197
  return loadCoValue(this, id, as, depth);
203
198
  }
204
199
 
205
- /** @category Subscription & Loading */
206
- static loadEf<S extends CoStream, Depth>(
207
- this: CoValueClass<S>,
208
- id: ID<S>,
209
- depth: Depth & DepthsIn<S>,
210
- ): Effect.Effect<DeeplyLoaded<S, Depth>, UnavailableError, AccountCtx> {
211
- return loadCoValueEf<S, Depth>(this, id, depth);
212
- }
213
-
214
200
  /** @category Subscription & Loading */
215
201
  static subscribe<S extends CoStream, Depth>(
216
202
  this: CoValueClass<S>,
@@ -222,15 +208,6 @@ export class CoStream<Item = any> extends CoValueBase implements CoValue {
222
208
  return subscribeToCoValue<S, Depth>(this, id, as, depth, listener);
223
209
  }
224
210
 
225
- /** @category Subscription & Loading */
226
- static subscribeEf<S extends CoStream, Depth>(
227
- this: CoValueClass<S>,
228
- id: ID<S>,
229
- depth: Depth & DepthsIn<S>,
230
- ): Stream.Stream<DeeplyLoaded<S, Depth>, UnavailableError, AccountCtx> {
231
- return subscribeToCoValueEf<S, Depth>(this, id, depth);
232
- }
233
-
234
211
  /** @category Subscription & Loading */
235
212
  ensureLoaded<S extends CoStream, Depth>(
236
213
  this: S,
@@ -638,15 +615,6 @@ export class BinaryCoStream extends CoValueBase implements CoValue {
638
615
  return loadCoValue(this, id, as, depth);
639
616
  }
640
617
 
641
- /** @category Subscription & Loading */
642
- static loadEf<B extends BinaryCoStream, Depth>(
643
- this: CoValueClass<B>,
644
- id: ID<B>,
645
- depth: Depth & DepthsIn<B>,
646
- ): Effect.Effect<DeeplyLoaded<B, Depth>, UnavailableError, AccountCtx> {
647
- return loadCoValueEf<B, Depth>(this, id, depth);
648
- }
649
-
650
618
  /** @category Subscription & Loading */
651
619
  static subscribe<B extends BinaryCoStream, Depth>(
652
620
  this: CoValueClass<B>,
@@ -658,15 +626,6 @@ export class BinaryCoStream extends CoValueBase implements CoValue {
658
626
  return subscribeToCoValue<B, Depth>(this, id, as, depth, listener);
659
627
  }
660
628
 
661
- /** @category Subscription & Loading */
662
- static subscribeEf<B extends BinaryCoStream, Depth>(
663
- this: CoValueClass<B>,
664
- id: ID<B>,
665
- depth: Depth & DepthsIn<B>,
666
- ): Stream.Stream<DeeplyLoaded<B, Depth>, UnavailableError, AccountCtx> {
667
- return subscribeToCoValueEf<B, Depth>(this, id, depth);
668
- }
669
-
670
629
  /** @category Subscription & Loading */
671
630
  ensureLoaded<B extends BinaryCoStream, Depth>(
672
631
  this: B,
@@ -4,11 +4,9 @@ import type {
4
4
  ID,
5
5
  RefEncoded,
6
6
  Schema,
7
- AccountCtx,
8
7
  CoValueClass,
9
8
  DeeplyLoaded,
10
9
  DepthsIn,
11
- UnavailableError,
12
10
  } from "../internal.js";
13
11
  import {
14
12
  Account,
@@ -20,13 +18,10 @@ import {
20
18
  AccountAndGroupProxyHandler,
21
19
  MembersSym,
22
20
  loadCoValue,
23
- loadCoValueEf,
24
21
  subscribeToCoValue,
25
- subscribeToCoValueEf,
26
22
  ensureCoValueLoaded,
27
23
  subscribeToExistingCoValue,
28
24
  } from "../internal.js";
29
- import { Effect, Stream } from "effect";
30
25
 
31
26
  /** @category Identity & Permissions */
32
27
  export class Profile extends CoMap {
@@ -198,15 +193,6 @@ export class Group extends CoValueBase implements CoValue {
198
193
  return loadCoValue(this, id, as, depth);
199
194
  }
200
195
 
201
- /** @category Subscription & Loading */
202
- static loadEf<G extends Group, Depth>(
203
- this: CoValueClass<G>,
204
- id: ID<G>,
205
- depth: Depth & DepthsIn<G>,
206
- ): Effect.Effect<DeeplyLoaded<G, Depth>, UnavailableError, AccountCtx> {
207
- return loadCoValueEf<G, Depth>(this, id, depth);
208
- }
209
-
210
196
  /** @category Subscription & Loading */
211
197
  static subscribe<G extends Group, Depth>(
212
198
  this: CoValueClass<G>,
@@ -218,15 +204,6 @@ export class Group extends CoValueBase implements CoValue {
218
204
  return subscribeToCoValue<G, Depth>(this, id, as, depth, listener);
219
205
  }
220
206
 
221
- /** @category Subscription & Loading */
222
- static subscribeEf<G extends Group, Depth>(
223
- this: CoValueClass<G>,
224
- id: ID<G>,
225
- depth: Depth & DepthsIn<G>,
226
- ): Stream.Stream<DeeplyLoaded<G, Depth>, UnavailableError, AccountCtx> {
227
- return subscribeToCoValueEf<G, Depth>(this, id, depth);
228
- }
229
-
230
207
  /** @category Subscription & Loading */
231
208
  ensureLoaded<G extends Group, Depth>(
232
209
  this: G,
@@ -1,10 +1,8 @@
1
- import { Effect, Option, Sink, Stream } from "effect";
2
1
  import type { CojsonInternalTypes, RawCoValue } from "cojson";
3
2
  import { RawAccount } from "cojson";
4
- import type { DeeplyLoaded, DepthsIn, UnavailableError } from "../internal.js";
3
+ import type { DeeplyLoaded, DepthsIn } from "../internal.js";
5
4
  import {
6
5
  Account,
7
- AccountCtx,
8
6
  Group,
9
7
  SubscriptionScope,
10
8
  Ref,
@@ -134,13 +132,22 @@ export function loadCoValue<V extends CoValue, Depth>(
134
132
  as: Account,
135
133
  depth: Depth & DepthsIn<V>,
136
134
  ): Promise<DeeplyLoaded<V, Depth> | undefined> {
137
- return Effect.runPromise(
138
- loadCoValueEf(cls, id, depth).pipe(
139
- Effect.mapError(() => undefined),
140
- Effect.merge,
141
- Effect.provideService(AccountCtx, as),
142
- ),
143
- );
135
+ return new Promise((resolve) => {
136
+ const unsubscribe = subscribeToCoValue(
137
+ cls,
138
+ id,
139
+ as,
140
+ depth,
141
+ (value) => {
142
+ resolve(value);
143
+ unsubscribe();
144
+ },
145
+ () => {
146
+ resolve(undefined);
147
+ unsubscribe();
148
+ },
149
+ );
150
+ });
144
151
  }
145
152
 
146
153
  export function ensureCoValueLoaded<V extends CoValue, Depth>(
@@ -155,41 +162,46 @@ export function ensureCoValueLoaded<V extends CoValue, Depth>(
155
162
  );
156
163
  }
157
164
 
158
- export function loadCoValueEf<V extends CoValue, Depth>(
159
- cls: CoValueClass<V>,
160
- id: ID<V>,
161
- depth: Depth & DepthsIn<V>,
162
- ): Effect.Effect<DeeplyLoaded<V, Depth>, UnavailableError, AccountCtx> {
163
- return subscribeToCoValueEf(cls, id, depth).pipe(
164
- Stream.runHead,
165
- Effect.andThen(
166
- Effect.mapError((_noSuchElem) => "unavailable" as const),
167
- ),
168
- );
169
- }
170
-
171
165
  export function subscribeToCoValue<V extends CoValue, Depth>(
172
166
  cls: CoValueClass<V>,
173
167
  id: ID<V>,
174
168
  as: Account,
175
169
  depth: Depth & DepthsIn<V>,
176
170
  listener: (value: DeeplyLoaded<V, Depth>) => void,
171
+ onUnavailable?: () => void,
177
172
  ): () => void {
178
- void Effect.runPromise(
179
- Effect.provideService(
180
- subscribeToCoValueEf(cls, id, depth).pipe(
181
- Stream.run(
182
- Sink.forEach((update) =>
183
- Effect.sync(() => listener(update)),
184
- ),
185
- ),
186
- ),
187
- AccountCtx,
188
- as,
189
- ),
190
- );
173
+ const ref = new Ref(id, as, { ref: cls, optional: false });
174
+
175
+ let unsubscribed = false;
176
+ let unsubscribe: (() => void) | undefined;
177
+
178
+ ref.load()
179
+ .then((value) => {
180
+ if (!value) {
181
+ onUnavailable && onUnavailable();
182
+ return;
183
+ }
184
+ if (unsubscribed) return;
185
+ const subscription = new SubscriptionScope(
186
+ value,
187
+ cls as CoValueClass<V> & CoValueFromRaw<V>,
188
+ (update) => {
189
+ if (fulfillsDepth(depth, update)) {
190
+ listener(update as DeeplyLoaded<V, Depth>);
191
+ }
192
+ },
193
+ );
194
+
195
+ unsubscribe = () => subscription.unsubscribeAll();
196
+ })
197
+ .catch((e) => {
198
+ console.error("Failed to load / subscribe to CoValue", e);
199
+ });
191
200
 
192
- return function unsubscribe() {};
201
+ return function unsubscribeAtAnyPoint() {
202
+ unsubscribed = true;
203
+ unsubscribe && unsubscribe();
204
+ };
193
205
  }
194
206
 
195
207
  export function subscribeToExistingCoValue<V extends CoValue, Depth>(
@@ -205,43 +217,3 @@ export function subscribeToExistingCoValue<V extends CoValue, Depth>(
205
217
  listener,
206
218
  );
207
219
  }
208
-
209
- export function subscribeToCoValueEf<V extends CoValue, Depth>(
210
- cls: CoValueClass<V>,
211
- id: ID<V>,
212
- depth: Depth & DepthsIn<V>,
213
- ): Stream.Stream<DeeplyLoaded<V, Depth>, UnavailableError, AccountCtx> {
214
- return AccountCtx.pipe(
215
- Effect.andThen((account) =>
216
- new Ref(id, account, {
217
- ref: cls,
218
- optional: false,
219
- }).loadEf(),
220
- ),
221
- Stream.fromEffect,
222
- Stream.flatMap((value: V) =>
223
- Stream.asyncScoped<V, UnavailableError>((emit) =>
224
- Effect.gen(function* (_) {
225
- const subscription = new SubscriptionScope(
226
- value,
227
- cls as CoValueClass<V> & CoValueFromRaw<V>,
228
- (update) => void emit.single(update as V),
229
- );
230
-
231
- yield* _(
232
- Effect.addFinalizer(() =>
233
- Effect.sync(() => subscription.unsubscribeAll()),
234
- ),
235
- );
236
- }),
237
- ),
238
- ),
239
- Stream.filterMap((update: V) =>
240
- Option.fromNullable(
241
- fulfillsDepth(depth, update)
242
- ? (update as DeeplyLoaded<V, Depth>)
243
- : undefined,
244
- ),
245
- ),
246
- );
247
- }
@@ -1,4 +1,3 @@
1
- import { Effect } from "effect";
2
1
  import type { CoID, RawCoValue } from "cojson";
3
2
  import type {
4
3
  Account,
@@ -6,7 +5,6 @@ import type {
6
5
  ID,
7
6
  RefEncoded,
8
7
  UnCo,
9
- UnavailableError,
10
8
  } from "../internal.js";
11
9
  import {
12
10
  instantiateRefEncoded,
@@ -47,22 +45,6 @@ export class Ref<out V extends CoValue> {
47
45
  }
48
46
  }
49
47
 
50
- loadEf() {
51
- return Effect.async<V, UnavailableError>((fulfill) => {
52
- this.loadHelper()
53
- .then((value) => {
54
- if (value === "unavailable") {
55
- fulfill(Effect.fail<UnavailableError>("unavailable"));
56
- } else {
57
- fulfill(Effect.succeed(value));
58
- }
59
- })
60
- .catch((e) => {
61
- fulfill(Effect.die(e));
62
- });
63
- });
64
- }
65
-
66
48
  private async loadHelper(options?: {
67
49
  onProgress: (p: number) => void;
68
50
  }): Promise<V | "unavailable"> {
@@ -23,7 +23,7 @@ const optional = {
23
23
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
24
24
  return { [SchemaInit]: "json" satisfies Schema } as any;
25
25
  },
26
- encoded<T>(arg: OptionalEncoder<T>): co<T> {
26
+ encoded<T>(arg: OptionalEncoder<T>): co<T | undefined> {
27
27
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
28
  return { [SchemaInit]: { encoded: arg } satisfies Schema } as any;
29
29
  },
package/src/index.ts CHANGED
@@ -26,9 +26,4 @@ export { ImageDefinition } from "./internal.js";
26
26
  export { CoValueBase, type CoValueClass } from "./internal.js";
27
27
  export type { DepthsIn, DeeplyLoaded } from "./internal.js";
28
28
 
29
- export {
30
- loadCoValue,
31
- loadCoValueEf,
32
- subscribeToCoValue,
33
- subscribeToCoValueEf,
34
- } from "./internal.js";
29
+ export { loadCoValue, subscribeToCoValue } from "./internal.js";
@@ -26,6 +26,7 @@ describe("Simple CoMap operations", async () => {
26
26
  birthday = co.encoded(Encoders.Date);
27
27
  name? = co.string;
28
28
  nullable = co.optional.encoded(Schema.NullishOr(Schema.String));
29
+ optionalDate = co.optional.encoded(Encoders.Date);
29
30
 
30
31
  get roughColor() {
31
32
  return this.color + "ish";
@@ -576,3 +577,163 @@ describe("CoMap resolution", async () => {
576
577
  ]);
577
578
  });
578
579
  });
580
+
581
+ describe("CoMap applyDiff", async () => {
582
+ const me = await Account.create({
583
+ creationProps: { name: "Tester McTesterson" },
584
+ crypto: Crypto,
585
+ });
586
+
587
+ class TestMap extends CoMap {
588
+ name = co.string;
589
+ age = co.number;
590
+ isActive = co.boolean;
591
+ birthday = co.encoded(Encoders.Date);
592
+ nested = co.ref(NestedMap);
593
+ optionalField = co.optional.string;
594
+ }
595
+
596
+ class NestedMap extends CoMap {
597
+ value = co.string;
598
+ }
599
+
600
+ test("Basic applyDiff", () => {
601
+ const map = TestMap.create(
602
+ {
603
+ name: "Alice",
604
+ age: 30,
605
+ isActive: true,
606
+ birthday: new Date("1990-01-01"),
607
+ nested: NestedMap.create({ value: "original" }, { owner: me }),
608
+ },
609
+ { owner: me },
610
+ );
611
+
612
+ const newValues = {
613
+ name: "Bob",
614
+ age: 35,
615
+ isActive: false,
616
+ };
617
+
618
+ map.applyDiff(newValues);
619
+
620
+ expect(map.name).toEqual("Bob");
621
+ expect(map.age).toEqual(35);
622
+ expect(map.isActive).toEqual(false);
623
+ expect(map.birthday).toEqual(new Date("1990-01-01"));
624
+ expect(map.nested?.value).toEqual("original");
625
+ });
626
+
627
+ test("applyDiff with nested changes", () => {
628
+ const map = TestMap.create(
629
+ {
630
+ name: "Charlie",
631
+ age: 25,
632
+ isActive: true,
633
+ birthday: new Date("1995-01-01"),
634
+ nested: NestedMap.create({ value: "original" }, { owner: me }),
635
+ },
636
+ { owner: me },
637
+ );
638
+
639
+ const newValues = {
640
+ name: "David",
641
+ nested: NestedMap.create({ value: "updated" }, { owner: me }),
642
+ };
643
+
644
+ map.applyDiff(newValues);
645
+
646
+ expect(map.name).toEqual("David");
647
+ expect(map.age).toEqual(25);
648
+ expect(map.nested?.value).toEqual("updated");
649
+ });
650
+
651
+ test("applyDiff with encoded fields", () => {
652
+ const map = TestMap.create(
653
+ {
654
+ name: "Eve",
655
+ age: 28,
656
+ isActive: true,
657
+ birthday: new Date("1993-01-01"),
658
+ nested: NestedMap.create({ value: "original" }, { owner: me }),
659
+ },
660
+ { owner: me },
661
+ );
662
+
663
+ const newValues = {
664
+ birthday: new Date("1993-06-15"),
665
+ };
666
+
667
+ map.applyDiff(newValues);
668
+
669
+ expect(map.birthday).toEqual(new Date("1993-06-15"));
670
+ });
671
+
672
+ test("applyDiff with optional fields", () => {
673
+ const map = TestMap.create(
674
+ {
675
+ name: "Frank",
676
+ age: 40,
677
+ isActive: true,
678
+ birthday: new Date("1980-01-01"),
679
+ nested: NestedMap.create({ value: "original" }, { owner: me }),
680
+ },
681
+ { owner: me },
682
+ );
683
+
684
+ const newValues = {
685
+ optionalField: "New optional value",
686
+ };
687
+
688
+ map.applyDiff(newValues);
689
+
690
+ expect(map.optionalField).toEqual("New optional value");
691
+
692
+ map.applyDiff({ optionalField: undefined });
693
+
694
+ expect(map.optionalField).toBeUndefined();
695
+ });
696
+
697
+ test("applyDiff with no changes", () => {
698
+ const map = TestMap.create(
699
+ {
700
+ name: "Grace",
701
+ age: 35,
702
+ isActive: true,
703
+ birthday: new Date("1985-01-01"),
704
+ nested: NestedMap.create({ value: "original" }, { owner: me }),
705
+ },
706
+ { owner: me },
707
+ );
708
+
709
+ const originalJSON = map.toJSON();
710
+
711
+ map.applyDiff({});
712
+
713
+ expect(map.toJSON()).toEqual(originalJSON);
714
+ });
715
+
716
+ test("applyDiff with invalid field", () => {
717
+ const map = TestMap.create(
718
+ {
719
+ name: "Henry",
720
+ age: 45,
721
+ isActive: false,
722
+ birthday: new Date("1975-01-01"),
723
+ nested: NestedMap.create({ value: "original" }, { owner: me }),
724
+ },
725
+ { owner: me },
726
+ );
727
+
728
+ const newValues = {
729
+ name: "Ian",
730
+ invalidField: "This should be ignored",
731
+ };
732
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
733
+ map.applyDiff(newValues as any);
734
+
735
+ expect(map.name).toEqual("Ian");
736
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
737
+ expect((map as any).invalidField).toBeUndefined();
738
+ });
739
+ });