jazz-tools 0.7.0-alpha.4 → 0.7.0-alpha.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/coValues/account.js +62 -29
  3. package/dist/coValues/account.js.map +1 -1
  4. package/dist/coValues/coList.js +139 -89
  5. package/dist/coValues/coList.js.map +1 -1
  6. package/dist/coValues/coMap.js +135 -151
  7. package/dist/coValues/coMap.js.map +1 -1
  8. package/dist/coValues/coStream.js +131 -57
  9. package/dist/coValues/coStream.js.map +1 -1
  10. package/dist/coValues/extensions/imageDef.js +10 -7
  11. package/dist/coValues/extensions/imageDef.js.map +1 -1
  12. package/dist/coValues/group.js +8 -30
  13. package/dist/coValues/group.js.map +1 -1
  14. package/dist/coValues/interfaces.js +7 -3
  15. package/dist/coValues/interfaces.js.map +1 -1
  16. package/dist/implementation/encoding.js +26 -0
  17. package/dist/implementation/encoding.js.map +1 -0
  18. package/dist/implementation/refs.js +11 -10
  19. package/dist/implementation/refs.js.map +1 -1
  20. package/dist/index.js +4 -3
  21. package/dist/index.js.map +1 -1
  22. package/dist/internal.js +1 -1
  23. package/dist/internal.js.map +1 -1
  24. package/dist/tests/coList.test.js +5 -9
  25. package/dist/tests/coList.test.js.map +1 -1
  26. package/dist/tests/coMap.test.js +87 -37
  27. package/dist/tests/coMap.test.js.map +1 -1
  28. package/dist/tests/coStream.test.js +46 -51
  29. package/dist/tests/coStream.test.js.map +1 -1
  30. package/package.json +2 -2
  31. package/src/coValues/account.ts +90 -60
  32. package/src/coValues/coList.ts +177 -114
  33. package/src/coValues/coMap.ts +191 -240
  34. package/src/coValues/coStream.ts +175 -97
  35. package/src/coValues/extensions/imageDef.ts +7 -12
  36. package/src/coValues/group.ts +24 -71
  37. package/src/coValues/interfaces.ts +10 -9
  38. package/src/implementation/encoding.ts +105 -0
  39. package/src/implementation/refs.ts +21 -20
  40. package/src/index.ts +3 -3
  41. package/src/internal.ts +1 -1
  42. package/src/tests/coList.test.ts +5 -9
  43. package/src/tests/coMap.test.ts +68 -52
  44. package/src/tests/coStream.test.ts +61 -66
  45. package/dist/implementation/schema.js +0 -6
  46. package/dist/implementation/schema.js.map +0 -1
  47. package/src/implementation/schema.ts +0 -69
@@ -0,0 +1,105 @@
1
+ import type { JsonValue, RawCoValue } from "cojson";
2
+ import type { CoValue, CoValueClass } from "../internal.js";
3
+ import type { Schema, TypeId } from "@effect/schema/Schema";
4
+
5
+ export type ValMarker = { readonly __field: unique symbol };
6
+ export type val<T> = T | (T & ValMarker);
7
+ export type IsVal<C, R> = C extends infer _A | infer B
8
+ ? B extends ValMarker
9
+ ? R
10
+ : never
11
+ : never;
12
+
13
+ export const SchemaInit = Symbol.for("SchemaInit");
14
+ export type SchemaInit = typeof SchemaInit;
15
+
16
+ export const InitValues = Symbol.for("InitValues");
17
+ export type InitValues = typeof InitValues;
18
+
19
+ export const ItemsSym = Symbol.for("items");
20
+ export type ItemsSym = typeof ItemsSym;
21
+
22
+ export const val = {
23
+ string: {[SchemaInit]: "json" satisfies Encoding } as unknown as val<string>,
24
+ number: {[SchemaInit]: "json" satisfies Encoding } as unknown as val<number>,
25
+ boolean: {[SchemaInit]: "json" satisfies Encoding } as unknown as val<boolean>,
26
+ literal: <T extends string | number | boolean>(
27
+ _lit: T
28
+ ): val<T> => {
29
+ return {[SchemaInit]: "json" satisfies Encoding } as any;
30
+ },
31
+ json: <T extends JsonValue>(): val<T> => {
32
+ return {[SchemaInit]: "json" satisfies Encoding } as any;
33
+ },
34
+ encoded: <T>(arg: Encoder<T>): val<T> => {
35
+ return { [SchemaInit]: { encoded: arg } satisfies Encoding } as any;
36
+ },
37
+ ref: <C extends CoValueClass>(
38
+ arg: (_raw: InstanceType<C>['_raw']) => C
39
+ ): val<InstanceType<C> | null> => {
40
+ return { [SchemaInit]: { ref: arg } satisfies Encoding } as any;
41
+ },
42
+ items: ItemsSym as ItemsSym,
43
+ }
44
+
45
+ export type JsonEncoded = "json";
46
+ export type EncodedAs<V> = { encoded: Encoder<V> };
47
+ export type RefEncoded<V extends CoValue> = {
48
+ ref: (raw: RawCoValue) => CoValueClass<V>;
49
+ };
50
+
51
+ export type Encoding = JsonEncoded | RefEncoded<CoValue> | EncodedAs<any>;
52
+
53
+ export type EncodingFor<Field> = NonNullable<Field> extends CoValue
54
+ ? RefEncoded<NonNullable<Field>>
55
+ : NonNullable<Field> extends JsonValue
56
+ ? JsonEncoded
57
+ : EncodedAs<NonNullable<Field>>;
58
+
59
+ export type EffectSchemaWithInputAndOutput<A, I = A> = Schema<
60
+ any,
61
+ any,
62
+ never
63
+ > & {
64
+ [TypeId]: {
65
+ _A: (_: any) => A;
66
+ _I: (_: any) => I;
67
+ };
68
+ };
69
+
70
+ export type Encoder<V> = EffectSchemaWithInputAndOutput<V, JsonValue>;
71
+
72
+ import { Date } from "@effect/schema/Schema";
73
+
74
+ export const Encoders = {
75
+ Date,
76
+ };
77
+
78
+ export type EnsureCoValueNullable<
79
+ V,
80
+ Key extends string | ItemsSym,
81
+ > = NonNullable<V> extends CoValue
82
+ ? null extends V
83
+ ? V
84
+ : Key extends string
85
+ ? [
86
+ `👋 CoMap fields that are CoValue references should be nullable, declare ${Key} as:`,
87
+ V | null,
88
+ ]
89
+ : [
90
+ `👋 CoMap fields that are CoValue references should be nullable, declare _item as:`,
91
+ V | null,
92
+ ]
93
+ : V;
94
+
95
+ export type ValidItem<
96
+ Item,
97
+ ContainerType extends string,
98
+ > = NonNullable<Item> extends CoValue
99
+ ? null extends Item
100
+ ? any
101
+ : [
102
+ `👋 CoList items that are CoValue references should be nullable, make sure the Item generic parameter of ${ContainerType} is:`,
103
+ Item | null,
104
+ ]
105
+ : any;
@@ -5,20 +5,23 @@ import type {
5
5
  CoValue,
6
6
  ID,
7
7
  Me,
8
- SubclassedConstructor,
8
+ RefEncoded,
9
9
  UnavailableError,
10
- indexSignature,
11
10
  } from "../internal.js";
12
11
  import { subscriptionsScopes } from "../internal.js";
13
12
 
14
- export class ValueRef<V extends CoValue> {
13
+ export class Ref<V extends CoValue> {
15
14
  private cachedValue: V | undefined;
16
15
 
17
16
  constructor(
18
17
  readonly id: ID<V>,
19
18
  readonly controlledAccount: Account & Me,
20
- readonly valueConstructor: SubclassedConstructor<V>
21
- ) {}
19
+ readonly encoding: RefEncoded<V>
20
+ ) {
21
+ if (!("ref" in encoding)) {
22
+ throw new Error("Ref must be constructed with a ref encoding");
23
+ }
24
+ }
22
25
 
23
26
  get value() {
24
27
  if (this.cachedValue) return this.cachedValue;
@@ -27,9 +30,7 @@ export class ValueRef<V extends CoValue> {
27
30
  this.id as unknown as CoID<RawCoValue>
28
31
  );
29
32
  if (raw) {
30
- const value = new this.valueConstructor(undefined, {
31
- fromRaw: raw,
32
- }) as V;
33
+ const value = this.encoding.ref(raw).fromRaw(raw);
33
34
  this.cachedValue = value;
34
35
  return value;
35
36
  } else {
@@ -63,10 +64,10 @@ export class ValueRef<V extends CoValue> {
63
64
  if (raw === "unavailable") {
64
65
  return "unavailable";
65
66
  } else {
66
- return new ValueRef(
67
+ return new Ref(
67
68
  this.id,
68
69
  this.controlledAccount,
69
- this.valueConstructor
70
+ this.encoding
70
71
  ).value!;
71
72
  }
72
73
  }
@@ -95,17 +96,17 @@ export class ValueRef<V extends CoValue> {
95
96
  }
96
97
  }
97
98
 
98
- export function makeRefs<Keys extends string | number | indexSignature>(
99
+ export function makeRefs<Keys extends string | number>(
99
100
  getIdForKey: (key: Keys) => ID<CoValue> | undefined,
100
101
  getKeysWithIds: () => Keys[],
101
102
  controlledAccount: Account & Me,
102
- valueConstructorForKey: (key: Keys) => SubclassedConstructor<CoValue>
103
- ): { [K in Keys]: ValueRef<CoValue> } & {
104
- [Symbol.iterator]: () => IterableIterator<ValueRef<CoValue>>;
103
+ refEncodingForKey: (key: Keys) => RefEncoded<CoValue>
104
+ ): { [K in Keys]: Ref<CoValue> } & {
105
+ [Symbol.iterator]: () => IterableIterator<Ref<CoValue>>;
105
106
  length: number;
106
107
  } {
107
- const refs = {} as { [K in Keys]: ValueRef<CoValue> } & {
108
- [Symbol.iterator]: () => IterableIterator<ValueRef<CoValue>>;
108
+ const refs = {} as { [K in Keys]: Ref<CoValue> } & {
109
+ [Symbol.iterator]: () => IterableIterator<Ref<CoValue>>;
109
110
  length: number;
110
111
  };
111
112
  return new Proxy(refs, {
@@ -113,10 +114,10 @@ export function makeRefs<Keys extends string | number | indexSignature>(
113
114
  if (key === Symbol.iterator) {
114
115
  return function* () {
115
116
  for (const key of getKeysWithIds()) {
116
- yield new ValueRef(
117
+ yield new Ref(
117
118
  getIdForKey(key)!,
118
119
  controlledAccount,
119
- valueConstructorForKey(key)
120
+ refEncodingForKey(key)
120
121
  );
121
122
  }
122
123
  };
@@ -127,10 +128,10 @@ export function makeRefs<Keys extends string | number | indexSignature>(
127
128
  }
128
129
  const id = getIdForKey(key as Keys);
129
130
  if (!id) return undefined;
130
- return new ValueRef(
131
+ return new Ref(
131
132
  id as ID<CoValue>,
132
133
  controlledAccount,
133
- valueConstructorForKey(key as Keys)
134
+ refEncodingForKey(key as Keys)
134
135
  );
135
136
  },
136
137
  ownKeys() {
package/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
- /** @category Internal types */
2
1
  export {
2
+ /** @category Internal types */
3
3
  cojsonReady as jazzReady,
4
4
  InviteSecret,
5
5
  Peer,
@@ -12,9 +12,9 @@ export {
12
12
 
13
13
  export { ID, CoValue } from "./internal.js";
14
14
 
15
- export { Encoders } from "./internal.js";
15
+ export { Encoders, val } from "./internal.js";
16
16
 
17
- export { CoMap, indexSignature } from "./internal.js";
17
+ export { CoMap } from "./internal.js";
18
18
  export { CoList } from "./internal.js";
19
19
  export { CoStream, BinaryCoStream } from "./internal.js";
20
20
  export { Group, Profile } from "./internal.js";
package/src/internal.ts CHANGED
@@ -9,7 +9,7 @@ export * from "./coValues/group.js";
9
9
 
10
10
  export * from "./implementation/errors.js";
11
11
  export * from "./implementation/refs.js";
12
- export * from "./implementation/schema.js";
12
+ export * from "./implementation/encoding.js";
13
13
  export * from "./implementation/subscriptionScope.js";
14
14
 
15
15
  export * from "./coValues/extensions/imageDef.js";
@@ -4,7 +4,7 @@ import { webcrypto } from "node:crypto";
4
4
  import { connectedPeers } from "cojson/src/streamUtils.js";
5
5
  import { newRandomSessionID } from "cojson/src/coValueCore.js";
6
6
  import { Effect, Queue } from "effect";
7
- import { Account, CoList, jazzReady } from "..";
7
+ import { Account, CoList, val, jazzReady } from "..";
8
8
 
9
9
  if (!("crypto" in globalThis)) {
10
10
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -20,8 +20,7 @@ describe("Simple CoList operations", async () => {
20
20
  name: "Hermes Puggington",
21
21
  });
22
22
 
23
- class TestList extends CoList<string> {}
24
- TestList.encoding({ _item: "json" });
23
+ class TestList extends CoList.Of(val.string) {}
25
24
 
26
25
  const list = new TestList(["bread", "butter", "onion"], { owner: me });
27
26
 
@@ -115,18 +114,15 @@ describe("Simple CoList operations", async () => {
115
114
  });
116
115
 
117
116
  describe("CoList resolution", async () => {
118
- class TwiceNestedList extends CoList<string> {
117
+ class TwiceNestedList extends CoList.Of(val.string) {
119
118
  joined() {
120
119
  return this.join(",");
121
120
  }
122
121
  }
123
- TwiceNestedList.encoding({ _item: "json" });
124
122
 
125
- class NestedList extends CoList<TwiceNestedList | null> {}
126
- NestedList.encoding({ _item: { ref: () => TwiceNestedList } });
123
+ class NestedList extends CoList.Of(val.ref(() => TwiceNestedList)) {}
127
124
 
128
- class TestList extends CoList<NestedList | null> {}
129
- TestList.encoding({ _item: { ref: () => NestedList } });
125
+ class TestList extends CoList.Of(val.ref(() => NestedList)) {}
130
126
 
131
127
  const initNodeAndList = async () => {
132
128
  const me = await Account.create({
@@ -4,7 +4,8 @@ import { webcrypto } from "node:crypto";
4
4
  import { connectedPeers } from "cojson/src/streamUtils.js";
5
5
  import { newRandomSessionID } from "cojson/src/coValueCore.js";
6
6
  import { Effect, Queue } from "effect";
7
- import { Account, jazzReady, Encoders, indexSignature, CoMap } from "..";
7
+ import { Account, jazzReady, Encoders, CoMap } from "..";
8
+ import { val } from "../internal";
8
9
 
9
10
  if (!("crypto" in globalThis)) {
10
11
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -21,21 +22,15 @@ describe("Simple CoMap operations", async () => {
21
22
  });
22
23
 
23
24
  class TestMap extends CoMap<TestMap> {
24
- declare color: string;
25
- declare height: number;
26
- declare birthday: Date;
27
- declare name?: string;
25
+ color = val.string;
26
+ _height = val.number;
27
+ birthday = val.encoded(Encoders.Date);
28
+ name? = val.string;
28
29
 
29
- get _roughColor() {
30
+ get roughColor() {
30
31
  return this.color + "ish";
31
32
  }
32
33
  }
33
- TestMap.encoding({
34
- color: "json",
35
- height: "json",
36
- birthday: { encoded: Encoders.Date },
37
- name: "json",
38
- });
39
34
 
40
35
  console.log("TestMap schema", TestMap.prototype._encoding);
41
36
 
@@ -44,7 +39,7 @@ describe("Simple CoMap operations", async () => {
44
39
  const map = new TestMap(
45
40
  {
46
41
  color: "red",
47
- height: 10,
42
+ _height: 10,
48
43
  birthday: birthday,
49
44
  },
50
45
  { owner: me }
@@ -52,10 +47,16 @@ describe("Simple CoMap operations", async () => {
52
47
 
53
48
  test("Construction", () => {
54
49
  expect(map.color).toEqual("red");
55
- expect(map._roughColor).toEqual("redish");
56
- expect(map.height).toEqual(10);
50
+ expect(map.roughColor).toEqual("redish");
51
+ expect(map._height).toEqual(10);
57
52
  expect(map.birthday).toEqual(birthday);
58
53
  expect(map._raw.get("birthday")).toEqual(birthday.toISOString());
54
+ expect(Object.keys(map)).toEqual([
55
+ "color",
56
+ "_height",
57
+ "birthday",
58
+ "name",
59
+ ]);
59
60
  });
60
61
 
61
62
  describe("Mutation", () => {
@@ -68,11 +69,11 @@ describe("Simple CoMap operations", async () => {
68
69
  expect(map.birthday).toEqual(newBirthday);
69
70
  expect(map._raw.get("birthday")).toEqual(newBirthday.toISOString());
70
71
 
71
- Object.assign(map, { color: "green", height: 20 });
72
+ Object.assign(map, { color: "green", _height: 20 });
72
73
  expect(map.color).toEqual("green");
73
74
  expect(map._raw.get("color")).toEqual("green");
74
- expect(map.height).toEqual(20);
75
- expect(map._raw.get("height")).toEqual(20);
75
+ expect(map._height).toEqual(20);
76
+ expect(map._raw.get("_height")).toEqual(20);
76
77
 
77
78
  map.name = "Secret name";
78
79
  expect(map.name).toEqual("Secret name");
@@ -82,13 +83,9 @@ describe("Simple CoMap operations", async () => {
82
83
  });
83
84
 
84
85
  class RecursiveMap extends CoMap<RecursiveMap> {
85
- declare name: string;
86
- declare next: RecursiveMap | null;
86
+ name = val.string;
87
+ next: val<RecursiveMap | null> = val.ref(() => RecursiveMap);
87
88
  }
88
- RecursiveMap.encoding({
89
- name: "json",
90
- next: { ref: () => RecursiveMap },
91
- });
92
89
 
93
90
  const recursiveMap = new RecursiveMap(
94
91
  {
@@ -116,43 +113,69 @@ describe("Simple CoMap operations", async () => {
116
113
  expect(recursiveMap.next?.next?.name).toEqual("third");
117
114
  });
118
115
  });
116
+
117
+ class MapWithEnumOfMaps extends CoMap<MapWithEnumOfMaps> {
118
+ name = val.string;
119
+ child = val.ref<typeof ChildA | typeof ChildB>((raw) =>
120
+ raw.get("type") === "a" ? ChildA : ChildB
121
+ );
122
+ }
123
+
124
+ class ChildA extends CoMap<ChildA> {
125
+ type = val.literal("a");
126
+ value = val.number;
127
+ }
128
+
129
+ class ChildB extends CoMap<ChildB> {
130
+ type = val.literal("b");
131
+ value = val.string;
132
+ }
133
+
134
+ const mapWithEnum = new MapWithEnumOfMaps(
135
+ {
136
+ name: "enum",
137
+ child: new ChildA(
138
+ {
139
+ type: "a",
140
+ value: 5,
141
+ },
142
+ { owner: me }
143
+ ),
144
+ },
145
+ { owner: me }
146
+ );
147
+
148
+ test("Enum of maps", () => {
149
+ expect(mapWithEnum.name).toEqual("enum");
150
+ expect(mapWithEnum.child?.type).toEqual("a");
151
+ expect(mapWithEnum.child?.value).toEqual(5);
152
+ expect(mapWithEnum.child?.id).toBeDefined();
153
+ });
119
154
  });
120
155
 
121
156
  describe("CoMap resolution", async () => {
122
157
  class TwiceNestedMap extends CoMap<TwiceNestedMap> {
123
- taste!: string;
158
+ taste = val.string;
124
159
  }
125
- TwiceNestedMap.encoding({
126
- taste: "json",
127
- });
128
160
 
129
161
  class NestedMap extends CoMap<NestedMap> {
130
- name!: string;
131
- twiceNested!: TwiceNestedMap | null;
162
+ name = val.string;
163
+ twiceNested = val.ref(() => TwiceNestedMap);
132
164
 
133
165
  get _fancyName() {
134
166
  return "Sir " + this.name;
135
167
  }
136
168
  }
137
- NestedMap.encoding({
138
- name: "json",
139
- twiceNested: { ref: () => TwiceNestedMap },
140
- });
141
169
 
142
170
  class TestMap extends CoMap<TestMap> {
143
- declare color: string;
144
- declare height: number;
145
- declare nested: NestedMap | null;
171
+ color = val.string;
172
+ height = val.number;
173
+ nested = val.ref(() => NestedMap);
146
174
 
147
175
  get _roughColor() {
148
176
  return this.color + "ish";
149
177
  }
150
178
  }
151
- TestMap.encoding({
152
- color: "json",
153
- height: "json",
154
- nested: { ref: () => NestedMap },
155
- });
156
179
 
157
180
  const initNodeAndMap = async () => {
158
181
  const me = await Account.create({
@@ -342,13 +365,9 @@ describe("CoMap resolution", async () => {
342
365
  });
343
366
 
344
367
  class TestMapWithOptionalRef extends CoMap<TestMapWithOptionalRef> {
345
- declare color: string;
346
- declare nested?: NestedMap | null;
368
+ color = val.string;
369
+ nested? = val.ref(() => NestedMap);
347
370
  }
348
- TestMapWithOptionalRef.encoding({
349
- color: "json",
350
- nested: { ref: () => NestedMap },
351
- });
352
371
 
353
372
  test("Construction with optional", async () => {
354
373
  const me = await Account.create({
@@ -389,12 +408,9 @@ describe("CoMap resolution", async () => {
389
408
  });
390
409
 
391
410
  class TestRecord extends CoMap<TestRecord> {
392
- declare [indexSignature]: number;
411
+ [val.items] = val.number;
393
412
  }
394
413
  interface TestRecord extends Record<string, number> {}
395
- TestRecord.encoding({
396
- [indexSignature]: "json",
397
- });
398
414
 
399
415
  test("Construction with index signature", async () => {
400
416
  const me = await Account.create({