jazz-tools 0.8.3 → 0.8.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/native/coValues/interfaces.js +15 -0
  3. package/dist/native/coValues/interfaces.js.map +1 -1
  4. package/dist/native/exports.js +11 -0
  5. package/dist/native/exports.js.map +1 -0
  6. package/dist/native/implementation/refs.js +4 -4
  7. package/dist/native/implementation/refs.js.map +1 -1
  8. package/dist/native/implementation/schema.js +14 -9
  9. package/dist/native/implementation/schema.js.map +1 -1
  10. package/dist/native/implementation/subscriptionScope.js +8 -9
  11. package/dist/native/implementation/subscriptionScope.js.map +1 -1
  12. package/dist/native/index.native.js +1 -10
  13. package/dist/native/index.native.js.map +1 -1
  14. package/dist/web/coValues/interfaces.js +15 -0
  15. package/dist/web/coValues/interfaces.js.map +1 -1
  16. package/dist/web/exports.js +11 -0
  17. package/dist/web/exports.js.map +1 -0
  18. package/dist/web/implementation/refs.js +4 -4
  19. package/dist/web/implementation/refs.js.map +1 -1
  20. package/dist/web/implementation/schema.js +14 -9
  21. package/dist/web/implementation/schema.js.map +1 -1
  22. package/dist/web/implementation/subscriptionScope.js +8 -9
  23. package/dist/web/implementation/subscriptionScope.js.map +1 -1
  24. package/dist/web/index.web.js +1 -10
  25. package/dist/web/index.web.js.map +1 -1
  26. package/package.json +6 -6
  27. package/src/coValues/interfaces.ts +34 -0
  28. package/src/exports.ts +35 -0
  29. package/src/implementation/refs.ts +3 -8
  30. package/src/implementation/schema.ts +21 -13
  31. package/src/implementation/subscriptionScope.ts +10 -11
  32. package/src/index.native.ts +2 -35
  33. package/src/index.web.ts +2 -35
  34. package/src/tests/coMap.test.ts +1 -1
  35. package/src/tests/schema.test.ts +205 -0
  36. package/src/tests/subscribe.test.ts +348 -0
  37. package/.turbo/turbo-build.log +0 -8
  38. package/.turbo/turbo-lint.log +0 -4
  39. package/.turbo/turbo-test.log +0 -144
package/src/exports.ts ADDED
@@ -0,0 +1,35 @@
1
+ export type {
2
+ InviteSecret,
3
+ Peer,
4
+ SessionID,
5
+ AgentID,
6
+ SyncMessage,
7
+ CryptoProvider,
8
+ CoValueUniqueness,
9
+ } from "cojson";
10
+
11
+ export type { ID, CoValue } from "./internal.js";
12
+
13
+ export { Encoders, co } from "./internal.js";
14
+
15
+ export { CoMap, type CoMapInit } from "./internal.js";
16
+ export { CoList } from "./internal.js";
17
+ export { CoStream, BinaryCoStream } from "./internal.js";
18
+ export { Group, Profile } from "./internal.js";
19
+ export { Account, isControlledAccount, type AccountClass } from "./internal.js";
20
+ export { ImageDefinition } from "./internal.js";
21
+ export { CoValueBase, type CoValueClass } from "./internal.js";
22
+ export type { DepthsIn, DeeplyLoaded } from "./internal.js";
23
+
24
+ export { loadCoValue, subscribeToCoValue, createCoValueObservable } from "./internal.js";
25
+
26
+ export {
27
+ type AuthMethod,
28
+ type AuthResult,
29
+ createJazzContext,
30
+ fixedCredentialsAuth,
31
+ ephemeralCredentialsAuth,
32
+ AnonymousJazzAgent,
33
+ createAnonymousJazzContext,
34
+ randomSessionProvider,
35
+ } from "./internal.js";
@@ -48,16 +48,13 @@ export class Ref<out V extends CoValue> {
48
48
  }
49
49
  }
50
50
 
51
- private async loadHelper(options?: {
52
- onProgress: (p: number) => void;
53
- }): Promise<V | "unavailable"> {
51
+ private async loadHelper(): Promise<V | "unavailable"> {
54
52
  const node =
55
53
  "node" in this.controlledAccount
56
54
  ? this.controlledAccount.node
57
55
  : this.controlledAccount._raw.core.node;
58
56
  const raw = await node.load(
59
57
  this.id as unknown as CoID<RawCoValue>,
60
- options?.onProgress,
61
58
  );
62
59
  if (raw === "unavailable") {
63
60
  return "unavailable";
@@ -66,10 +63,8 @@ export class Ref<out V extends CoValue> {
66
63
  }
67
64
  }
68
65
 
69
- async load(options?: {
70
- onProgress: (p: number) => void;
71
- }): Promise<V | undefined> {
72
- const result = await this.loadHelper(options);
66
+ async load(): Promise<V | undefined> {
67
+ const result = await this.loadHelper();
73
68
  if (result === "unavailable") {
74
69
  return undefined;
75
70
  } else {
@@ -4,7 +4,19 @@ import {
4
4
  type CoValueClass,
5
5
  isCoValueClass,
6
6
  CoValueFromRaw,
7
+ SchemaInit,
8
+ ItemsSym,
9
+ MembersSym,
7
10
  } from "../internal.js";
11
+ import { CoJsonValue } from "cojson/src/jsonValue.js";
12
+
13
+ /** @category Schema definition */
14
+ export const Encoders = {
15
+ Date: {
16
+ encode: (value: Date) => value.toISOString(),
17
+ decode: (value: JsonValue) => new Date(value as string),
18
+ },
19
+ };
8
20
 
9
21
  export type CoMarker = { readonly __co: unique symbol };
10
22
  /** @category Schema definition */
@@ -18,7 +30,7 @@ export type UnCo<T> = T extends co<infer A> ? A : T;
18
30
 
19
31
  const optional = {
20
32
  ref: optionalRef,
21
- json<T extends JsonValue>(): co<T | undefined> {
33
+ json<T extends CoJsonValue<T>>(): co<T | undefined> {
22
34
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
23
35
  return { [SchemaInit]: "json" satisfies Schema } as any;
24
36
  },
@@ -38,6 +50,9 @@ const optional = {
38
50
  null: {
39
51
  [SchemaInit]: "json" satisfies Schema,
40
52
  } as unknown as co<null | undefined>,
53
+ Date: {
54
+ [SchemaInit]: { encoded: Encoders.Date } satisfies Schema,
55
+ } as unknown as co<Date | undefined>,
41
56
  literal<T extends (string | number | boolean)[]>(
42
57
  ..._lit: T
43
58
  ): co<T[number] | undefined> {
@@ -60,13 +75,16 @@ export const co = {
60
75
  null: {
61
76
  [SchemaInit]: "json" satisfies Schema,
62
77
  } as unknown as co<null>,
78
+ Date: {
79
+ [SchemaInit]: { encoded: Encoders.Date } satisfies Schema,
80
+ } as unknown as co<Date>,
63
81
  literal<T extends (string | number | boolean)[]>(
64
82
  ..._lit: T
65
83
  ): co<T[number]> {
66
84
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
67
85
  return { [SchemaInit]: "json" satisfies Schema } as any;
68
86
  },
69
- json<T extends JsonValue>(): co<T> {
87
+ json<T extends CoJsonValue<T>>(): co<T> {
70
88
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
71
89
  return { [SchemaInit]: "json" satisfies Schema } as any;
72
90
  },
@@ -155,20 +173,10 @@ export type SchemaFor<Field> = NonNullable<Field> extends CoValue
155
173
  export type Encoder<V> = {
156
174
  encode: (value: V) => JsonValue;
157
175
  decode: (value: JsonValue) => V;
158
- };
176
+ }
159
177
  export type OptionalEncoder<V> =
160
178
  | Encoder<V>
161
179
  | {
162
180
  encode: (value: V | undefined) => JsonValue;
163
181
  decode: (value: JsonValue) => V | undefined;
164
182
  };
165
-
166
- import { SchemaInit, ItemsSym, MembersSym } from "./symbols.js";
167
-
168
- /** @category Schema definition */
169
- export const Encoders = {
170
- Date: {
171
- encode: (value: Date) => value.toISOString(),
172
- decode: (value: JsonValue) => new Date(value as string),
173
- },
174
- };
@@ -26,10 +26,10 @@ export class SubscriptionScope<Root extends CoValue> {
26
26
  >();
27
27
  rootEntry: {
28
28
  state: "loaded";
29
- value: Root;
29
+ value: RawCoValue;
30
30
  rawUnsub: () => void;
31
31
  };
32
- onUpdate: (newRoot: Root) => void;
32
+ scheduleUpdate: () => void;
33
33
  scheduledUpdate: boolean = false;
34
34
  cachedValues: { [id: ID<CoValue>]: CoValue } = {};
35
35
  parents: { [id: ID<CoValue>]: Set<ID<CoValue>> } = {};
@@ -41,7 +41,7 @@ export class SubscriptionScope<Root extends CoValue> {
41
41
  ) {
42
42
  this.rootEntry = {
43
43
  state: "loaded" as const,
44
- value: root,
44
+ value: root._raw,
45
45
  rawUnsub: () => {}, // placeholder
46
46
  };
47
47
  this.entries.set(root.id, this.rootEntry);
@@ -49,22 +49,21 @@ export class SubscriptionScope<Root extends CoValue> {
49
49
  subscriptionsScopes.set(root, this);
50
50
 
51
51
  this.subscriber = root._loadedAs;
52
- this.onUpdate = onUpdate;
52
+ this.scheduleUpdate = () => {
53
+ const value = rootSchema.fromRaw(this.rootEntry.value) as Root;
54
+ subscriptionsScopes.set(value, this);
55
+ onUpdate(value);
56
+ };
57
+
53
58
  this.rootEntry.rawUnsub = root._raw.core.subscribe(
54
59
  (rawUpdate: RawCoValue | undefined) => {
55
60
  if (!rawUpdate) return;
56
- this.rootEntry.value = rootSchema.fromRaw(rawUpdate) as Root;
57
- // console.log("root update", this.rootEntry.value.toJSON());
58
- subscriptionsScopes.set(this.rootEntry.value, this);
61
+ this.rootEntry.value = rawUpdate;
59
62
  this.scheduleUpdate();
60
63
  },
61
64
  );
62
65
  }
63
66
 
64
- scheduleUpdate() {
65
- this.onUpdate(this.rootEntry.value);
66
- }
67
-
68
67
  onRefAccessedOrSet(
69
68
  fromId: ID<CoValue>,
70
69
  accessedOrSetId: ID<CoValue> | undefined,
@@ -1,40 +1,7 @@
1
+ export * from "./exports.js";
2
+
1
3
  export {
2
4
  cojsonInternals,
3
5
  MAX_RECOMMENDED_TX_SIZE,
4
6
  PureJSCrypto,
5
7
  } from "cojson/native";
6
- export type {
7
- InviteSecret,
8
- Peer,
9
- SessionID,
10
- AgentID,
11
- SyncMessage,
12
- CryptoProvider,
13
- CoValueUniqueness,
14
- } from "cojson";
15
-
16
- export type { ID, CoValue } from "./internal.js";
17
-
18
- export { Encoders, co } from "./internal.js";
19
-
20
- export { CoMap, type CoMapInit } from "./internal.js";
21
- export { CoList } from "./internal.js";
22
- export { CoStream, BinaryCoStream } from "./internal.js";
23
- export { Group, Profile } from "./internal.js";
24
- export { Account, isControlledAccount, type AccountClass } from "./internal.js";
25
- export { ImageDefinition } from "./internal.js";
26
- export { CoValueBase, type CoValueClass } from "./internal.js";
27
- export type { DepthsIn, DeeplyLoaded } from "./internal.js";
28
-
29
- export { loadCoValue, subscribeToCoValue } from "./internal.js";
30
-
31
- export {
32
- type AuthMethod,
33
- type AuthResult,
34
- createJazzContext,
35
- fixedCredentialsAuth,
36
- ephemeralCredentialsAuth,
37
- AnonymousJazzAgent,
38
- createAnonymousJazzContext,
39
- randomSessionProvider,
40
- } from "./internal.js";
package/src/index.web.ts CHANGED
@@ -1,36 +1,3 @@
1
- export { cojsonInternals, MAX_RECOMMENDED_TX_SIZE, WasmCrypto } from "cojson";
2
- export type {
3
- InviteSecret,
4
- Peer,
5
- SessionID,
6
- AgentID,
7
- SyncMessage,
8
- CryptoProvider,
9
- CoValueUniqueness,
10
- } from "cojson";
11
-
12
- export type { ID, CoValue } from "./internal.js";
13
-
14
- export { Encoders, co } from "./internal.js";
1
+ export * from "./exports.js";
15
2
 
16
- export { CoMap, type CoMapInit } from "./internal.js";
17
- export { CoList } from "./internal.js";
18
- export { CoStream, BinaryCoStream } from "./internal.js";
19
- export { Group, Profile } from "./internal.js";
20
- export { Account, isControlledAccount, type AccountClass } from "./internal.js";
21
- export { ImageDefinition } from "./internal.js";
22
- export { CoValueBase, type CoValueClass } from "./internal.js";
23
- export type { DepthsIn, DeeplyLoaded } from "./internal.js";
24
-
25
- export { loadCoValue, subscribeToCoValue } from "./internal.js";
26
-
27
- export {
28
- type AuthMethod,
29
- type AuthResult,
30
- createJazzContext,
31
- fixedCredentialsAuth,
32
- ephemeralCredentialsAuth,
33
- AnonymousJazzAgent,
34
- createAnonymousJazzContext,
35
- randomSessionProvider,
36
- } from "./internal.js";
3
+ export { cojsonInternals, MAX_RECOMMENDED_TX_SIZE, WasmCrypto } from "cojson";
@@ -18,7 +18,7 @@ const Crypto = await WasmCrypto.create();
18
18
  class TestMap extends CoMap {
19
19
  color = co.string;
20
20
  _height = co.number;
21
- birthday = co.encoded(Encoders.Date);
21
+ birthday = co.Date;
22
22
  name? = co.string;
23
23
  nullable = co.optional.encoded<string | undefined>({
24
24
  encode: (value: string | undefined) => value || null,
@@ -0,0 +1,205 @@
1
+ import { describe, expectTypeOf, it } from "vitest";
2
+ import { CoMap, co } from "../index.web.js";
3
+ import { co as valueWithCoMarker } from "../internal.js";
4
+
5
+ describe("co.json TypeScript validation", () => {
6
+ it("should accept serializable types", async () => {
7
+ type ValidType = { str: string; num: number; bool: boolean };
8
+
9
+ class ValidPrimitiveMap extends CoMap {
10
+ data = co.json<ValidType>();
11
+ }
12
+
13
+ expectTypeOf(ValidPrimitiveMap.create<ValidPrimitiveMap>)
14
+ .parameter(0)
15
+ .toEqualTypeOf<{
16
+ data: valueWithCoMarker<ValidType>;
17
+ }>();
18
+ });
19
+
20
+ it("should accept nested serializable types", async () => {
21
+ type NestedType = {
22
+ outer: {
23
+ inner: {
24
+ value: string;
25
+ };
26
+ };
27
+ }
28
+
29
+ class ValidNestedMap extends CoMap {
30
+ data = co.json<NestedType>();
31
+ }
32
+
33
+ expectTypeOf(ValidNestedMap.create<ValidNestedMap>)
34
+ .parameter(0)
35
+ .toEqualTypeOf<{
36
+ data: valueWithCoMarker<NestedType>;
37
+ }>();
38
+ });
39
+
40
+ it("should accept types with optional attributes", async () => {
41
+ type TypeWithOptional = {
42
+ value: string;
43
+ optional?: string | null;
44
+ }
45
+
46
+ class ValidMap extends CoMap {
47
+ data = co.json<TypeWithOptional>();
48
+ }
49
+
50
+ expectTypeOf(ValidMap.create<ValidMap>)
51
+ .parameter(0)
52
+ .toEqualTypeOf<{
53
+ data: valueWithCoMarker<TypeWithOptional>;
54
+ }>();
55
+ });
56
+
57
+ it("should accept nested serializable interfaces", async () => {
58
+ interface InnerInterface {
59
+ value: string;
60
+ }
61
+
62
+ interface NestedInterface {
63
+ outer: {
64
+ inner: InnerInterface;
65
+ };
66
+ }
67
+
68
+ class ValidNestedMap extends CoMap {
69
+ data = co.json<NestedInterface>();
70
+ }
71
+
72
+ expectTypeOf(ValidNestedMap.create<ValidNestedMap>)
73
+ .parameter(0)
74
+ .toEqualTypeOf<{
75
+ data: valueWithCoMarker<NestedInterface>;
76
+ }>();
77
+ });
78
+
79
+ it("should accept arrays of serializable types", async () => {
80
+ interface ArrayInterface {
81
+ numbers: number[];
82
+ objects: { id: number; name: string }[];
83
+ }
84
+
85
+ class ValidArrayMap extends CoMap {
86
+ data = co.json<ArrayInterface>();
87
+ }
88
+
89
+ expectTypeOf(ValidArrayMap.create<ValidArrayMap>)
90
+ .parameter(0)
91
+ .toEqualTypeOf<{
92
+ data: valueWithCoMarker<ArrayInterface>;
93
+ }>();
94
+ });
95
+
96
+ it("should flag interfaces with functions as invalid", async () => {
97
+ interface InvalidInterface {
98
+ func: () => void;
99
+ }
100
+
101
+ class InvalidFunctionMap extends CoMap {
102
+ // @ts-expect-error Should not be considered valid
103
+ data = co.json<InvalidInterface>();
104
+ }
105
+
106
+ expectTypeOf(InvalidFunctionMap.create<InvalidFunctionMap>)
107
+ .parameter(0)
108
+ .toEqualTypeOf<{
109
+ data: valueWithCoMarker<InvalidInterface>;
110
+ }>();
111
+ });
112
+
113
+ it("should flag types with functions as invalid", async () => {
114
+ type InvalidType = { func: () => void };
115
+
116
+ class InvalidFunctionMap extends CoMap {
117
+ // @ts-expect-error Should not be considered valid
118
+ data = co.json<InvalidType>();
119
+ }
120
+
121
+ expectTypeOf(InvalidFunctionMap.create<InvalidFunctionMap>)
122
+ .parameter(0)
123
+ .toEqualTypeOf<{
124
+ data: valueWithCoMarker<InvalidType>;
125
+ }>();
126
+ });
127
+
128
+ it("should flag types with non-serializable constructors as invalid", async () => {
129
+ type InvalidType = { date: Date; regexp: RegExp; symbol: symbol };
130
+
131
+ class InvalidFunctionMap extends CoMap {
132
+ // @ts-expect-error Should not be considered valid
133
+ data = co.json<InvalidType>();
134
+ }
135
+
136
+ expectTypeOf(InvalidFunctionMap.create<InvalidFunctionMap>)
137
+ .parameter(0)
138
+ .toEqualTypeOf<{
139
+ data: valueWithCoMarker<InvalidType>;
140
+ }>();
141
+ });
142
+
143
+ it("should apply the same validation to optional json", async () => {
144
+ type ValidType = {
145
+ value: string;
146
+ };
147
+
148
+ type InvalidType = {
149
+ value: () => string;
150
+ };
151
+
152
+ class MapWithOptionalJSON extends CoMap {
153
+ data = co.optional.json<ValidType>();
154
+ // @ts-expect-error Should not be considered valid
155
+ data2 = co.optional.json<InvalidType>();
156
+ }
157
+
158
+ expectTypeOf(MapWithOptionalJSON.create<MapWithOptionalJSON>)
159
+ .parameter(0)
160
+ .toEqualTypeOf<{
161
+ data?: valueWithCoMarker<ValidType> | null;
162
+ data2?: valueWithCoMarker<InvalidType> | null;
163
+ }>();
164
+ });
165
+
166
+ it("should not accept functions", async () => {
167
+ class InvalidFunctionMap extends CoMap {
168
+ // @ts-expect-error Should not be considered valid
169
+ data = co.json<() => void>();
170
+ }
171
+
172
+ expectTypeOf(InvalidFunctionMap.create<InvalidFunctionMap>)
173
+ .parameter(0)
174
+ .toEqualTypeOf<{
175
+ data: valueWithCoMarker<() => void>;
176
+ }>();
177
+ });
178
+
179
+ it("should not accept RegExp", async () => {
180
+ class InvalidFunctionMap extends CoMap {
181
+ // @ts-expect-error Should not be considered valid
182
+ data = co.json<RegExp>();
183
+ }
184
+
185
+ expectTypeOf(InvalidFunctionMap.create<InvalidFunctionMap>)
186
+ .parameter(0)
187
+ .toEqualTypeOf<{
188
+ data: valueWithCoMarker<RegExp>;
189
+ }>();
190
+ });
191
+
192
+ it("should accept strings and numbers", async () => {
193
+ class InvalidFunctionMap extends CoMap {
194
+ str = co.json<string>();
195
+ num = co.json<number>();
196
+ }
197
+
198
+ expectTypeOf(InvalidFunctionMap.create<InvalidFunctionMap>)
199
+ .parameter(0)
200
+ .toEqualTypeOf<{
201
+ str: valueWithCoMarker<string>;
202
+ num: valueWithCoMarker<number>;
203
+ }>();
204
+ });
205
+ });