jazz-tools 0.7.25 → 0.7.28

Sign up to get free protection for your applications and to get access to all the features.
@@ -35,7 +35,6 @@ import {
35
35
  ensureCoValueLoaded,
36
36
  subscribeToExistingCoValue,
37
37
  } from "../internal.js";
38
- import { encodeSync, decodeSync } from "@effect/schema/Schema";
39
38
 
40
39
  export type CoStreamEntry<Item> = SingleCoStreamEntry<Item> & {
41
40
  all: IterableIterator<SingleCoStreamEntry<Item>>;
@@ -141,7 +140,7 @@ export class CoStream<Item = any> extends CoValueBase implements CoValue {
141
140
  if (itemDescriptor === "json") {
142
141
  this._raw.push(item as JsonValue);
143
142
  } else if ("encoded" in itemDescriptor) {
144
- this._raw.push(encodeSync(itemDescriptor.encoded)(item));
143
+ this._raw.push(itemDescriptor.encoded.encode(item));
145
144
  } else if (isRefEncoded(itemDescriptor)) {
146
145
  this._raw.push((item as unknown as CoValue).id);
147
146
  }
@@ -153,7 +152,7 @@ export class CoStream<Item = any> extends CoValueBase implements CoValue {
153
152
  itemDescriptor === "json"
154
153
  ? (v: unknown) => v
155
154
  : "encoded" in itemDescriptor
156
- ? encodeSync(itemDescriptor.encoded)
155
+ ? itemDescriptor.encoded.encode
157
156
  : (v: unknown) => v && (v as CoValue).id;
158
157
 
159
158
  return {
@@ -247,7 +246,7 @@ function entryFromRawEntry<Item>(
247
246
  ? (CoValue & Item) | null
248
247
  : Item;
249
248
  } else if ("encoded" in itemField) {
250
- return decodeSync(itemField.encoded)(rawEntry.value);
249
+ return itemField.encoded.decode(rawEntry.value);
251
250
  } else if (isRefEncoded(itemField)) {
252
251
  return this.ref?.accessFrom(
253
252
  accessFrom,
@@ -122,7 +122,12 @@ export class CoValueBase implements CoValue {
122
122
  castAs<Cl extends CoValueClass & CoValueFromRaw<CoValue>>(
123
123
  cl: Cl,
124
124
  ): InstanceType<Cl> {
125
- return cl.fromRaw(this._raw) as InstanceType<Cl>;
125
+ const casted = cl.fromRaw(this._raw) as InstanceType<Cl>;
126
+ const subscriptionScope = subscriptionsScopes.get(this);
127
+ if (subscriptionScope) {
128
+ subscriptionsScopes.set(casted, subscriptionScope);
129
+ }
130
+ return casted;
126
131
  }
127
132
  }
128
133
 
@@ -5,7 +5,6 @@ import {
5
5
  isCoValueClass,
6
6
  CoValueFromRaw,
7
7
  } from "../internal.js";
8
- import type { Schema as EffectSchema, TypeId } from "@effect/schema/Schema";
9
8
 
10
9
  export type CoMarker = { readonly __co: unique symbol };
11
10
  /** @category Schema definition */
@@ -113,7 +112,7 @@ function ref<
113
112
  }
114
113
 
115
114
  export type JsonEncoded = "json";
116
- export type EncodedAs<V> = { encoded: Encoder<V> };
115
+ export type EncodedAs<V> = { encoded: Encoder<V> | OptionalEncoder<V> };
117
116
  export type RefEncoded<V extends CoValue> = {
118
117
  ref: CoValueClass<V> | ((raw: RawCoValue) => CoValueClass<V>);
119
118
  optional: boolean;
@@ -152,31 +151,23 @@ export type SchemaFor<Field> = NonNullable<Field> extends CoValue
152
151
  ? JsonEncoded
153
152
  : EncodedAs<NonNullable<Field>>;
154
153
 
155
- export type EffectSchemaWithInputAndOutput<A, I = A> = EffectSchema<
156
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
157
- any,
158
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
159
- any,
160
- never
161
- > & {
162
- [TypeId]: {
163
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
164
- _A: (_: any) => A;
165
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
166
- _I: (_: any) => I;
167
- };
154
+ export type Encoder<V> = {
155
+ encode: (value: V) => JsonValue;
156
+ decode: (value: JsonValue) => V;
168
157
  };
158
+ export type OptionalEncoder<V> =
159
+ | Encoder<V>
160
+ | {
161
+ encode: (value: V | undefined) => JsonValue;
162
+ decode: (value: JsonValue) => V | undefined;
163
+ };
169
164
 
170
- export type Encoder<V> = EffectSchemaWithInputAndOutput<V, JsonValue>;
171
- export type OptionalEncoder<V> = EffectSchemaWithInputAndOutput<
172
- V,
173
- JsonValue | undefined
174
- >;
175
-
176
- import { Date } from "@effect/schema/Schema";
177
165
  import { SchemaInit, ItemsSym, MembersSym } from "./symbols.js";
178
166
 
179
167
  /** @category Schema definition */
180
168
  export const Encoders = {
181
- Date,
169
+ Date: {
170
+ encode: (value: Date) => value.toISOString(),
171
+ decode: (value: JsonValue) => new Date(value as string),
172
+ },
182
173
  };
@@ -1,12 +1,12 @@
1
1
  import { expect, describe, test } from "vitest";
2
2
  import { connectedPeers } from "cojson/src/streamUtils.js";
3
3
  import { newRandomSessionID } from "cojson/src/coValueCore.js";
4
- import { Effect, Queue } from "effect";
5
4
  import {
6
5
  Account,
7
6
  CoList,
8
7
  WasmCrypto,
9
8
  co,
9
+ cojsonInternals,
10
10
  isControlledAccount,
11
11
  } from "../index.js";
12
12
 
@@ -157,11 +157,13 @@ describe("CoList resolution", async () => {
157
157
  test("Loading and availability", async () => {
158
158
  const { me, list } = await initNodeAndList();
159
159
 
160
- const [initialAsPeer, secondPeer] = await Effect.runPromise(
161
- connectedPeers("initial", "second", {
160
+ const [initialAsPeer, secondPeer] = connectedPeers(
161
+ "initial",
162
+ "second",
163
+ {
162
164
  peer1role: "server",
163
165
  peer2role: "client",
164
- }),
166
+ },
165
167
  );
166
168
  if (!isControlledAccount(me)) {
167
169
  throw "me is not a controlled account";
@@ -217,11 +219,13 @@ describe("CoList resolution", async () => {
217
219
  test("Subscription & auto-resolution", async () => {
218
220
  const { me, list } = await initNodeAndList();
219
221
 
220
- const [initialAsPeer, secondPeer] = await Effect.runPromise(
221
- connectedPeers("initial", "second", {
222
+ const [initialAsPeer, secondPeer] = connectedPeers(
223
+ "initial",
224
+ "second",
225
+ {
222
226
  peer1role: "server",
223
227
  peer2role: "client",
224
- }),
228
+ },
225
229
  );
226
230
  if (!isControlledAccount(me)) {
227
231
  throw "me is not a controlled account";
@@ -236,63 +240,52 @@ describe("CoList resolution", async () => {
236
240
  crypto: Crypto,
237
241
  });
238
242
 
239
- await Effect.runPromise(
240
- Effect.gen(function* ($) {
241
- const queue = yield* $(Queue.unbounded<TestList>());
242
-
243
- TestList.subscribe(
244
- list.id,
245
- meOnSecondPeer,
246
- [],
247
- (subscribedList) => {
248
- console.log(
249
- "subscribedList?.[0]?.[0]?.[0]",
250
- subscribedList?.[0]?.[0]?.[0],
251
- );
252
- void Effect.runPromise(
253
- Queue.offer(queue, subscribedList),
254
- );
255
- },
256
- );
257
-
258
- const update1 = yield* $(Queue.take(queue));
259
- expect(update1?.[0]).toBe(null);
260
-
261
- const update2 = yield* $(Queue.take(queue));
262
- expect(update2?.[0]).toBeDefined();
263
- expect(update2?.[0]?.[0]).toBe(null);
264
-
265
- const update3 = yield* $(Queue.take(queue));
266
- expect(update3?.[0]?.[0]).toBeDefined();
267
- expect(update3?.[0]?.[0]?.[0]).toBe("a");
268
- expect(update3?.[0]?.[0]?.joined()).toBe("a,b");
269
-
270
- update3[0]![0]![0] = "x";
271
-
272
- const update4 = yield* $(Queue.take(queue));
273
- expect(update4?.[0]?.[0]?.[0]).toBe("x");
274
-
275
- // When assigning a new nested value, we get an update
276
-
277
- const newTwiceNestedList = TwiceNestedList.create(["y", "z"], {
278
- owner: meOnSecondPeer,
279
- });
280
-
281
- const newNestedList = NestedList.create([newTwiceNestedList], {
282
- owner: meOnSecondPeer,
283
- });
284
-
285
- update4[0] = newNestedList;
286
-
287
- const update5 = yield* $(Queue.take(queue));
288
- expect(update5?.[0]?.[0]?.[0]).toBe("y");
289
- expect(update5?.[0]?.[0]?.joined()).toBe("y,z");
290
-
291
- // we get updates when the new nested value changes
292
- newTwiceNestedList[0] = "w";
293
- const update6 = yield* $(Queue.take(queue));
294
- expect(update6?.[0]?.[0]?.[0]).toBe("w");
295
- }),
296
- );
243
+ const queue = new cojsonInternals.Channel();
244
+
245
+ TestList.subscribe(list.id, meOnSecondPeer, [], (subscribedList) => {
246
+ console.log(
247
+ "subscribedList?.[0]?.[0]?.[0]",
248
+ subscribedList?.[0]?.[0]?.[0],
249
+ );
250
+ void queue.push(subscribedList);
251
+ });
252
+
253
+ const update1 = (await queue.next()).value;
254
+ expect(update1?.[0]).toBe(null);
255
+
256
+ const update2 = (await queue.next()).value;
257
+ expect(update2?.[0]).toBeDefined();
258
+ expect(update2?.[0]?.[0]).toBe(null);
259
+
260
+ const update3 = (await queue.next()).value;
261
+ expect(update3?.[0]?.[0]).toBeDefined();
262
+ expect(update3?.[0]?.[0]?.[0]).toBe("a");
263
+ expect(update3?.[0]?.[0]?.joined()).toBe("a,b");
264
+
265
+ update3[0]![0]![0] = "x";
266
+
267
+ const update4 = (await queue.next()).value;
268
+ expect(update4?.[0]?.[0]?.[0]).toBe("x");
269
+
270
+ // When assigning a new nested value, we get an update
271
+
272
+ const newTwiceNestedList = TwiceNestedList.create(["y", "z"], {
273
+ owner: meOnSecondPeer,
274
+ });
275
+
276
+ const newNestedList = NestedList.create([newTwiceNestedList], {
277
+ owner: meOnSecondPeer,
278
+ });
279
+
280
+ update4[0] = newNestedList;
281
+
282
+ const update5 = (await queue.next()).value;
283
+ expect(update5?.[0]?.[0]?.[0]).toBe("y");
284
+ expect(update5?.[0]?.[0]?.joined()).toBe("y,z");
285
+
286
+ // we get updates when the new nested value changes
287
+ newTwiceNestedList[0] = "w";
288
+ const update6 = (await queue.next()).value;
289
+ expect(update6?.[0]?.[0]?.[0]).toBe("w");
297
290
  });
298
291
  });
@@ -1,7 +1,6 @@
1
1
  import { expect, describe, test } from "vitest";
2
2
  import { connectedPeers } from "cojson/src/streamUtils.js";
3
3
  import { newRandomSessionID } from "cojson/src/coValueCore.js";
4
- import { Effect, Queue } from "effect";
5
4
  import {
6
5
  Account,
7
6
  Encoders,
@@ -9,8 +8,8 @@ import {
9
8
  co,
10
9
  WasmCrypto,
11
10
  isControlledAccount,
11
+ cojsonInternals,
12
12
  } from "../index.js";
13
- import { Schema } from "@effect/schema";
14
13
 
15
14
  const Crypto = await WasmCrypto.create();
16
15
 
@@ -25,7 +24,10 @@ describe("Simple CoMap operations", async () => {
25
24
  _height = co.number;
26
25
  birthday = co.encoded(Encoders.Date);
27
26
  name? = co.string;
28
- nullable = co.optional.encoded(Schema.NullishOr(Schema.String));
27
+ nullable = co.optional.encoded<string | undefined>({
28
+ encode: (value: string | undefined) => value || null,
29
+ decode: (value: unknown) => (value as string) || undefined,
30
+ });
29
31
  optionalDate = co.optional.encoded(Encoders.Date);
30
32
 
31
33
  get roughColor() {
@@ -42,7 +44,7 @@ describe("Simple CoMap operations", async () => {
42
44
  color: "red",
43
45
  _height: 10,
44
46
  birthday: birthday,
45
- nullable: null,
47
+ nullable: undefined,
46
48
  },
47
49
  { owner: me },
48
50
  );
@@ -94,7 +96,7 @@ describe("Simple CoMap operations", async () => {
94
96
  expect(map._raw.get("_height")).toEqual(20);
95
97
 
96
98
  map.nullable = "not null";
97
- map.nullable = null;
99
+ map.nullable = undefined;
98
100
  delete map.nullable;
99
101
  map.nullable = undefined;
100
102
 
@@ -282,12 +284,12 @@ describe("CoMap resolution", async () => {
282
284
 
283
285
  test("Loading and availability", async () => {
284
286
  const { me, map } = await initNodeAndMap();
285
- const [initialAsPeer, secondPeer] = await Effect.runPromise(
287
+ const [initialAsPeer, secondPeer] =
286
288
  connectedPeers("initial", "second", {
287
289
  peer1role: "server",
288
290
  peer2role: "client",
289
- }),
290
- );
291
+ });
292
+
291
293
  if (!isControlledAccount(me)) {
292
294
  throw "me is not a controlled account";
293
295
  }
@@ -353,12 +355,12 @@ describe("CoMap resolution", async () => {
353
355
  test("Subscription & auto-resolution", async () => {
354
356
  const { me, map } = await initNodeAndMap();
355
357
 
356
- const [initialAsPeer, secondAsPeer] = await Effect.runPromise(
358
+ const [initialAsPeer, secondAsPeer] =
357
359
  connectedPeers("initial", "second", {
358
360
  peer1role: "server",
359
361
  peer2role: "client",
360
- }),
361
- );
362
+ });
363
+
362
364
  if (!isControlledAccount(me)) {
363
365
  throw "me is not a controlled account";
364
366
  }
@@ -372,9 +374,8 @@ describe("CoMap resolution", async () => {
372
374
  crypto: Crypto,
373
375
  });
374
376
 
375
- await Effect.runPromise(
376
- Effect.gen(function* ($) {
377
- const queue = yield* $(Queue.unbounded<TestMap>());
377
+
378
+ const queue = new cojsonInternals.Channel<TestMap>();
378
379
 
379
380
  TestMap.subscribe(
380
381
  map.id,
@@ -385,22 +386,20 @@ describe("CoMap resolution", async () => {
385
386
  "subscribedMap.nested?.twiceNested?.taste",
386
387
  subscribedMap.nested?.twiceNested?.taste,
387
388
  );
388
- void Effect.runPromise(
389
- Queue.offer(queue, subscribedMap),
390
- );
389
+ void queue.push(subscribedMap);
391
390
  },
392
391
  );
393
392
 
394
- const update1 = yield* $(Queue.take(queue));
393
+ const update1 = (await queue.next()).value;
395
394
  expect(update1.nested).toEqual(null);
396
395
 
397
- const update2 = yield* $(Queue.take(queue));
396
+ const update2 = (await queue.next()).value;
398
397
  expect(update2.nested?.name).toEqual("nested");
399
398
 
400
399
  map.nested!.name = "nestedUpdated";
401
400
 
402
- const _ = yield* $(Queue.take(queue));
403
- const update3 = yield* $(Queue.take(queue));
401
+ const _ = (await queue.next()).value;
402
+ const update3 = (await queue.next()).value;
404
403
  expect(update3.nested?.name).toEqual("nestedUpdated");
405
404
 
406
405
  const oldTwiceNested = update3.nested!.twiceNested;
@@ -424,23 +423,21 @@ describe("CoMap resolution", async () => {
424
423
 
425
424
  update3.nested = newNested;
426
425
 
427
- yield* $(Queue.take(queue));
428
- // const update4 = yield* $(Queue.take(queue));
429
- const update4b = yield* $(Queue.take(queue));
426
+ (await queue.next()).value;
427
+ // const update4 = (await queue.next()).value;
428
+ const update4b = (await queue.next()).value;
430
429
 
431
430
  expect(update4b.nested?.name).toEqual("newNested");
432
431
  expect(update4b.nested?.twiceNested?.taste).toEqual("sweet");
433
432
 
434
433
  // we get updates when the new nested value changes
435
434
  newTwiceNested.taste = "salty";
436
- const update5 = yield* $(Queue.take(queue));
435
+ const update5 = (await queue.next()).value;
437
436
  expect(update5.nested?.twiceNested?.taste).toEqual("salty");
438
437
 
439
438
  newTwiceNested.taste = "umami";
440
- const update6 = yield* $(Queue.take(queue));
439
+ const update6 = (await queue.next()).value;
441
440
  expect(update6.nested?.twiceNested?.taste).toEqual("umami");
442
- }),
443
- );
444
441
  });
445
442
 
446
443
  class TestMapWithOptionalRef extends CoMap {