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

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. package/.eslintrc.cjs +3 -10
  2. package/.prettierrc.js +9 -0
  3. package/.turbo/turbo-build.log +3 -19
  4. package/.turbo/turbo-lint.log +4 -0
  5. package/.turbo/turbo-test.log +140 -0
  6. package/CHANGELOG.md +243 -0
  7. package/README.md +10 -2
  8. package/dist/coValues/account.js +104 -50
  9. package/dist/coValues/account.js.map +1 -1
  10. package/dist/coValues/coList.js +165 -112
  11. package/dist/coValues/coList.js.map +1 -1
  12. package/dist/coValues/coMap.js +243 -163
  13. package/dist/coValues/coMap.js.map +1 -1
  14. package/dist/coValues/coStream.js +256 -73
  15. package/dist/coValues/coStream.js.map +1 -1
  16. package/dist/coValues/deepLoading.js +57 -0
  17. package/dist/coValues/deepLoading.js.map +1 -0
  18. package/dist/coValues/extensions/imageDef.js +14 -8
  19. package/dist/coValues/extensions/imageDef.js.map +1 -1
  20. package/dist/coValues/group.js +49 -38
  21. package/dist/coValues/group.js.map +1 -1
  22. package/dist/coValues/interfaces.js +66 -26
  23. package/dist/coValues/interfaces.js.map +1 -1
  24. package/dist/implementation/devtoolsFormatters.js +114 -0
  25. package/dist/implementation/devtoolsFormatters.js.map +1 -0
  26. package/dist/implementation/refs.js +60 -19
  27. package/dist/implementation/refs.js.map +1 -1
  28. package/dist/implementation/schema.js +44 -1
  29. package/dist/implementation/schema.js.map +1 -1
  30. package/dist/implementation/subscriptionScope.js +19 -1
  31. package/dist/implementation/subscriptionScope.js.map +1 -1
  32. package/dist/implementation/symbols.js +5 -0
  33. package/dist/implementation/symbols.js.map +1 -0
  34. package/dist/index.js +4 -5
  35. package/dist/index.js.map +1 -1
  36. package/dist/internal.js +4 -1
  37. package/dist/internal.js.map +1 -1
  38. package/dist/tests/coList.test.js +51 -52
  39. package/dist/tests/coList.test.js.map +1 -1
  40. package/dist/tests/coMap.test.js +196 -75
  41. package/dist/tests/coMap.test.js.map +1 -1
  42. package/dist/tests/coStream.test.js +95 -85
  43. package/dist/tests/coStream.test.js.map +1 -1
  44. package/dist/tests/deepLoading.test.js +188 -0
  45. package/dist/tests/deepLoading.test.js.map +1 -0
  46. package/dist/tests/groupsAndAccounts.test.js +83 -0
  47. package/dist/tests/groupsAndAccounts.test.js.map +1 -0
  48. package/package.json +17 -9
  49. package/src/coValues/account.ts +183 -152
  50. package/src/coValues/coList.ts +220 -173
  51. package/src/coValues/coMap.ts +322 -312
  52. package/src/coValues/coStream.ts +397 -135
  53. package/src/coValues/deepLoading.ts +215 -0
  54. package/src/coValues/extensions/imageDef.ts +16 -17
  55. package/src/coValues/group.ts +95 -111
  56. package/src/coValues/interfaces.ts +217 -115
  57. package/src/implementation/devtoolsFormatters.ts +110 -0
  58. package/src/implementation/inspect.ts +1 -1
  59. package/src/implementation/refs.ts +91 -38
  60. package/src/implementation/schema.ts +87 -46
  61. package/src/implementation/subscriptionScope.ts +44 -12
  62. package/src/implementation/symbols.ts +11 -0
  63. package/src/index.ts +13 -9
  64. package/src/internal.ts +6 -2
  65. package/src/tests/coList.test.ts +67 -66
  66. package/src/tests/coMap.test.ts +226 -123
  67. package/src/tests/coStream.test.ts +141 -131
  68. package/src/tests/deepLoading.test.ts +301 -0
  69. package/src/tests/groupsAndAccounts.test.ts +91 -0
@@ -0,0 +1,215 @@
1
+ import { SessionID } from "cojson";
2
+ import {
3
+ Account,
4
+ CoList,
5
+ CoStream,
6
+ CoStreamEntry,
7
+ ItemsSym,
8
+ Ref,
9
+ UnCo,
10
+ } from "../internal.js";
11
+ import { CoKeys } from "./coMap.js";
12
+ import { CoValue, ID } from "./interfaces.js";
13
+
14
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
+ export function fulfillsDepth(depth: any, value: CoValue): boolean {
16
+ if (
17
+ value._type === "CoMap" ||
18
+ value._type === "Group" ||
19
+ value._type === "Account"
20
+ ) {
21
+ if (Array.isArray(depth) && depth.length === 1) {
22
+ return Object.entries(value).every(([key, item]) => {
23
+ return (
24
+ value as unknown as {
25
+ _refs: { [key: string]: Ref<CoValue> | undefined };
26
+ }
27
+ )._refs[key]
28
+ ? item && fulfillsDepth(depth[0], item)
29
+ : true;
30
+ });
31
+ } else {
32
+ for (const key of Object.keys(depth)) {
33
+ const map = value as unknown as {
34
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
35
+ [key: string]: any;
36
+ _refs: { [key: string]: Ref<CoValue> | undefined };
37
+ };
38
+ if (!map._refs[key]) {
39
+ continue;
40
+ }
41
+ if (!map[key]) {
42
+ return false;
43
+ }
44
+ if (!fulfillsDepth(depth[key], map[key])) {
45
+ return false;
46
+ }
47
+ }
48
+ return true;
49
+ }
50
+ } else if (value._type === "CoList") {
51
+ if (depth.length === 0) {
52
+ return true;
53
+ } else {
54
+ const itemDepth = depth[0];
55
+ return (value as CoList).every((item, i) =>
56
+ (value as CoList)._refs[i]
57
+ ? item && fulfillsDepth(itemDepth, item)
58
+ : true,
59
+ );
60
+ }
61
+ } else if (value._type === "CoStream") {
62
+ if (depth.length === 0) {
63
+ return true;
64
+ } else {
65
+ const itemDepth = depth[0];
66
+ return Object.values((value as CoStream).perSession).every(
67
+ (entry) => entry.value && fulfillsDepth(itemDepth, entry.value),
68
+ );
69
+ }
70
+ } else if (value._type === "BinaryCoStream") {
71
+ return true;
72
+ } else {
73
+ console.error(value);
74
+ throw new Error("Unexpected value type: " + value._type);
75
+ }
76
+ }
77
+
78
+ type UnCoNotNull<T> = UnCo<Exclude<T, null>>;
79
+ type Clean<T> = UnCo<NonNullable<T>>;
80
+
81
+ export type DepthsIn<
82
+ V,
83
+ DepthLimit extends number = 5,
84
+ CurrentDepth extends number[] = [],
85
+ > =
86
+ | (DepthLimit extends CurrentDepth["length"]
87
+ ? // eslint-disable-next-line @typescript-eslint/no-explicit-any
88
+ any
89
+ : // Basically V extends CoList - but if we used that we'd introduce circularity into the definition of CoList itself
90
+ V extends Array<infer Item>
91
+ ?
92
+ | [
93
+ DepthsIn<
94
+ UnCoNotNull<Item>,
95
+ DepthLimit,
96
+ [0, ...CurrentDepth]
97
+ >,
98
+ ]
99
+ | never[]
100
+ : // Basically V extends CoMap | Group | Account - but if we used that we'd introduce circularity into the definition of CoMap itself
101
+ V extends { _type: "CoMap" | "Group" | "Account" }
102
+ ?
103
+ | {
104
+ [Key in CoKeys<V> as Clean<V[Key]> extends CoValue
105
+ ? Key
106
+ : never]?: DepthsIn<
107
+ Clean<V[Key]>,
108
+ DepthLimit,
109
+ [0, ...CurrentDepth]
110
+ >;
111
+ }
112
+ | (ItemsSym extends keyof V
113
+ ? [
114
+ DepthsIn<
115
+ Clean<V[ItemsSym]>,
116
+ DepthLimit,
117
+ [0, ...CurrentDepth]
118
+ >,
119
+ ]
120
+ : never)
121
+ | never[]
122
+ : V extends {
123
+ _type: "CoStream";
124
+ byMe: CoStreamEntry<infer Item> | undefined;
125
+ }
126
+ ?
127
+ | [
128
+ DepthsIn<
129
+ UnCoNotNull<Item>,
130
+ DepthLimit,
131
+ [0, ...CurrentDepth]
132
+ >,
133
+ ]
134
+ | never[]
135
+ : never[])
136
+ | never[];
137
+
138
+ export type DeeplyLoaded<
139
+ V,
140
+ Depth,
141
+ DepthLimit extends number = 5,
142
+ CurrentDepth extends number[] = [],
143
+ > = DepthLimit extends CurrentDepth["length"]
144
+ ? V
145
+ : // Basically V extends CoList - but if we used that we'd introduce circularity into the definition of CoList itself
146
+ [V] extends [Array<infer Item>]
147
+ ? Depth extends never[] // []
148
+ ? V
149
+ : UnCoNotNull<Item> extends CoValue
150
+ ? Depth extends Array<infer ItemDepth> // [item-depth]
151
+ ? (UnCoNotNull<Item> &
152
+ DeeplyLoaded<
153
+ UnCoNotNull<Item>,
154
+ ItemDepth,
155
+ DepthLimit,
156
+ [0, ...CurrentDepth]
157
+ >)[] &
158
+ V
159
+ : never
160
+ : V
161
+ : // Basically V extends CoMap | Group | Account - but if we used that we'd introduce circularity into the definition of CoMap itself
162
+ [V] extends [{ _type: "CoMap" | "Group" | "Account" }]
163
+ ? Depth extends never[]
164
+ ? V
165
+ : Depth extends Array<infer ItemDepth>
166
+ ? ItemsSym extends keyof V
167
+ ? V & {
168
+ [key: string]: DeeplyLoaded<
169
+ Clean<V[ItemsSym]>,
170
+ ItemDepth,
171
+ DepthLimit,
172
+ [0, ...CurrentDepth]
173
+ >;
174
+ }
175
+ : never
176
+ : keyof Depth extends never
177
+ ? V
178
+ : {
179
+ [Key in keyof Depth]-?: Key extends CoKeys<V>
180
+ ? Clean<V[Key]> extends CoValue
181
+ ?
182
+ | DeeplyLoaded<
183
+ Clean<V[Key]>,
184
+ Depth[Key],
185
+ DepthLimit,
186
+ [0, ...CurrentDepth]
187
+ >
188
+ | (undefined extends V[Key]
189
+ ? undefined
190
+ : never)
191
+ : never
192
+ : never;
193
+ } & V
194
+ : [V] extends [
195
+ {
196
+ _type: "CoStream";
197
+ byMe: CoStreamEntry<infer Item> | undefined;
198
+ },
199
+ ]
200
+ ? Depth extends never[]
201
+ ? V
202
+ : V & {
203
+ byMe?: { value: UnCoNotNull<Item> };
204
+ inCurrentSession?: { value: UnCoNotNull<Item> };
205
+ perSession: {
206
+ [key: SessionID]: { value: UnCoNotNull<Item> };
207
+ };
208
+ } & { [key: ID<Account>]: { value: UnCoNotNull<Item> } }
209
+ : [V] extends [
210
+ {
211
+ _type: "BinaryCoStream";
212
+ },
213
+ ]
214
+ ? V
215
+ : never;
@@ -1,28 +1,32 @@
1
1
  import {
2
2
  BinaryCoStream,
3
3
  CoMap,
4
- indexSignature,
4
+ co,
5
5
  subscriptionsScopes,
6
6
  } from "../../internal.js";
7
7
 
8
- export class ImageDefinition extends CoMap<ImageDefinition> {
9
- declare originalSize: [number, number];
10
- declare placeholderDataURL?: string;
8
+ /** @category Media */
9
+ export class ImageDefinition extends CoMap {
10
+ originalSize = co.json<[number, number]>();
11
+ placeholderDataURL? = co.string;
11
12
 
12
- [res: `${number}x${number}`]: BinaryCoStream | null;
13
- declare [indexSignature]: BinaryCoStream | null;
13
+ [co.items] = co.ref(BinaryCoStream);
14
+ [res: `${number}x${number}`]: co<BinaryCoStream | null>;
14
15
 
15
- get _highestResAvailable():
16
- | { res: `${number}x${number}`; stream: BinaryCoStream }
17
- | undefined {
16
+ highestResAvailable(options?: {
17
+ maxWidth?: number;
18
+ }): { res: `${number}x${number}`; stream: BinaryCoStream } | undefined {
18
19
  if (!subscriptionsScopes.get(this)) {
19
20
  console.warn(
20
- "highestResAvailable() only makes sense when used within a subscription."
21
+ "highestResAvailable() only makes sense when used within a subscription.",
21
22
  );
22
23
  }
23
24
 
24
- const resolutions = Object.keys(this).filter((key) =>
25
- key.match(/^\d+x\d+$/)
25
+ const resolutions = Object.keys(this).filter(
26
+ (key) =>
27
+ key.match(/^\d+x\d+$/) &&
28
+ (options?.maxWidth === undefined ||
29
+ Number(key.split("x")[0]) <= options.maxWidth),
26
30
  ) as `${number}x${number}`[];
27
31
 
28
32
  resolutions.sort((a, b) => {
@@ -53,8 +57,3 @@ export class ImageDefinition extends CoMap<ImageDefinition> {
53
57
  );
54
58
  }
55
59
  }
56
- ImageDefinition.encoding({
57
- originalSize: "json",
58
- placeholderDataURL: "json",
59
- [indexSignature]: { ref: () => BinaryCoStream },
60
- });
@@ -1,119 +1,97 @@
1
- import type { CoID, Everyone, RawCoMap, RawGroup, Role } from "cojson";
2
- import type {
3
- CoValue,
4
- CoValueClass,
5
- ID,
6
- PrimitiveField,
7
- RefField,
8
- } from "../internal.js";
1
+ import type { AccountID, Everyone, RawGroup, Role } from "cojson";
2
+ import type { CoValue, ID, RefEncoded, Schema, ClassOf } from "../internal.js";
9
3
  import {
10
4
  Account,
11
5
  CoMap,
12
6
  CoValueBase,
13
- ValueRef,
7
+ Ref,
8
+ co,
14
9
  isControlledAccount,
10
+ AccountAndGroupProxyHandler,
11
+ MembersSym,
15
12
  } from "../internal.js";
16
13
 
17
- export class Profile extends CoMap<{ name: string }> {
18
- declare name: string;
14
+ /** @category Identity & Permissions */
15
+ export class Profile extends CoMap {
16
+ name = co.string;
19
17
  }
20
- Profile.encoding({ name: "json" });
21
18
 
22
- export class Group<
23
- Def extends { profile: Profile | null; root: CoMap | null } = {
24
- profile: Profile | null;
25
- root: CoMap | null;
26
- },
27
- >
28
- extends CoValueBase
29
- implements CoValue<"Group", RawGroup>
30
- {
31
- id!: ID<this>;
32
- _type!: "Group";
19
+ /** @category Identity & Permissions */
20
+ export class Group extends CoValueBase implements CoValue<"Group", RawGroup> {
21
+ declare id: ID<this>;
22
+ declare _type: "Group";
33
23
  static {
34
24
  this.prototype._type = "Group";
35
25
  }
36
- _raw!: RawGroup;
26
+ declare _raw: RawGroup;
37
27
 
38
- static _encoding: any;
39
- get _encoding(): {
40
- profile: Def["profile"] extends CoValue
41
- ? RefField<Def["profile"]>
42
- : PrimitiveField;
43
- root: Def["root"] extends CoValue
44
- ? RefField<Def["root"]>
45
- : PrimitiveField;
28
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
+ static _schema: any;
30
+ get _schema(): {
31
+ profile: Schema;
32
+ root: Schema;
33
+ [MembersSym]: RefEncoded<Account>;
46
34
  } {
47
- return (this.constructor as typeof Group)._encoding;
35
+ return (this.constructor as typeof Group)._schema;
48
36
  }
49
37
  static {
50
- this._encoding = {
51
- profile: { json: true },
52
- root: { json: true },
38
+ this._schema = {
39
+ profile: "json" satisfies Schema,
40
+ root: "json" satisfies Schema,
41
+ [MembersSym]: () => Account satisfies Schema,
42
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
53
43
  } as any;
54
- Object.defineProperty(this.prototype, "_encoding", {
55
- get: () => this._encoding,
44
+ Object.defineProperty(this.prototype, "_schema", {
45
+ get: () => this._schema,
56
46
  });
57
47
  }
58
48
 
59
- profile!: Def["profile"] extends Profile
60
- ? Def["profile"] | null
61
- : undefined;
62
-
63
- root!: Def["root"] extends CoMap ? Def["root"] | null : undefined;
49
+ declare profile: Profile | null;
50
+ declare root: CoMap | null;
51
+ declare [MembersSym]: Account | null;
64
52
 
65
- get _refs(): {
66
- profile: Def["profile"] extends Profile
67
- ? ValueRef<Def["profile"]>
68
- : never;
69
- root: Def["root"] extends CoMap ? ValueRef<Def["root"]> : never;
70
- } {
53
+ get _refs() {
71
54
  const profileID = this._raw.get("profile") as unknown as
72
- | ID<NonNullable<Def["profile"]>>
55
+ | ID<NonNullable<this["profile"]>>
73
56
  | undefined;
74
57
  const rootID = this._raw.get("root") as unknown as
75
- | ID<NonNullable<Def["root"]>>
58
+ | ID<NonNullable<this["root"]>>
76
59
  | undefined;
77
60
  return {
78
61
  profile:
79
62
  profileID &&
80
- (new ValueRef(
63
+ (new Ref(
81
64
  profileID,
82
65
  this._loadedAs,
83
- (
84
- this._encoding.profile as RefField<
85
- NonNullable<Def["profile"]>
86
- >
87
- ).ref()
88
- ) as any),
66
+ this._schema.profile as RefEncoded<
67
+ NonNullable<this["profile"]>
68
+ >,
69
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
70
+ ) as any as this["profile"] extends Profile
71
+ ? Ref<this["profile"]>
72
+ : never),
89
73
  root:
90
74
  rootID &&
91
- (new ValueRef(
75
+ (new Ref(
92
76
  rootID,
93
77
  this._loadedAs,
94
- (
95
- this._encoding.root as RefField<
96
- NonNullable<Def["root"]>
97
- >
98
- ).ref()
99
- ) as any),
78
+ this._schema.root as RefEncoded<NonNullable<this["root"]>>,
79
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
80
+ ) as any as this["root"] extends CoMap
81
+ ? Ref<this["root"]>
82
+ : never),
100
83
  };
101
84
  }
102
85
 
103
- constructor(options: { owner: Account | Group });
104
- constructor(init: any, options: { fromRaw: RawGroup });
105
- constructor(init: undefined, options: { owner: Account | Group });
106
- constructor(
107
- init: undefined | { owner: Account | Group },
108
- options?: { fromRaw: RawGroup } | { owner: Account | Group }
109
- ) {
86
+ /** @deprecated Don't use constructor directly, use .create */
87
+ constructor(options: { fromRaw: RawGroup } | { owner: Account | Group }) {
110
88
  super();
111
89
  let raw: RawGroup;
112
90
 
113
91
  if (options && "fromRaw" in options) {
114
92
  raw = options.fromRaw;
115
93
  } else {
116
- const initOwner = options?.owner || init?.owner;
94
+ const initOwner = options.owner;
117
95
  if (!initOwner) throw new Error("No owner provided");
118
96
  if (
119
97
  initOwner instanceof Account &&
@@ -123,7 +101,7 @@ export class Group<
123
101
  raw = rawOwner.createGroup();
124
102
  } else {
125
103
  throw new Error(
126
- "Can only construct group as a controlled account"
104
+ "Can only construct group as a controlled account",
127
105
  );
128
106
  }
129
107
  }
@@ -134,35 +112,19 @@ export class Group<
134
112
  enumerable: false,
135
113
  },
136
114
  _raw: { value: raw, enumerable: false },
137
- profile: {
138
- get: () => {
139
- const ref = this._refs.profile;
140
- return ref ? ref.accessFrom(this) : (undefined as any);
141
- },
142
- set: (value: Def["profile"] | null) => {
143
- if (value) {
144
- this._raw.set(
145
- "profile",
146
- value.id as unknown as CoID<RawCoMap>
147
- );
148
- }
149
- },
150
- },
151
- root: {
152
- get: () => {
153
- const ref = this._refs.root;
154
- return ref ? ref.accessFrom(this) : (undefined as any);
155
- },
156
- set: (value: Def["root"] | null) => {
157
- if (value) {
158
- this._raw.set(
159
- "root",
160
- value.id as unknown as CoID<RawCoMap>
161
- );
162
- }
163
- },
164
- },
165
115
  });
116
+
117
+ return new Proxy(
118
+ this,
119
+ AccountAndGroupProxyHandler as ProxyHandler<this>,
120
+ );
121
+ }
122
+
123
+ static create<G extends Group>(
124
+ this: ClassOf<G>,
125
+ options: { owner: Account },
126
+ ) {
127
+ return new this(options);
166
128
  }
167
129
 
168
130
  myRole(): Role | undefined {
@@ -171,16 +133,38 @@ export class Group<
171
133
 
172
134
  addMember(member: Everyone | Account, role: Role) {
173
135
  this._raw.addMember(member === "everyone" ? member : member._raw, role);
136
+ return this;
174
137
  }
175
138
 
176
- static define<V extends Account>(
177
- this: CoValueClass<V> & typeof Account,
178
- fields: {
179
- profile: V["_encoding"]["profile"];
180
- root: V["_encoding"]["root"];
181
- }
182
- ) {
183
- this._encoding ||= {};
184
- Object.assign(this._encoding, fields);
139
+ get members() {
140
+ return this._raw
141
+ .keys()
142
+ .filter((key) => {
143
+ return key === "everyone" || key.startsWith("co_");
144
+ })
145
+ .map((id) => {
146
+ const role = this._raw.get(id as Everyone | AccountID);
147
+ const accountID =
148
+ id === "everyone"
149
+ ? undefined
150
+ : (id as unknown as ID<Account>);
151
+ const ref =
152
+ accountID &&
153
+ new Ref<NonNullable<this[MembersSym]>>(
154
+ accountID,
155
+ this._loadedAs,
156
+ this._schema[MembersSym],
157
+ );
158
+ const accessRef = () => ref?.accessFrom(this, "members." + id);
159
+
160
+ return {
161
+ id: id as unknown as Everyone | ID<this[MembersSym]>,
162
+ role,
163
+ ref,
164
+ get account() {
165
+ return accessRef();
166
+ },
167
+ };
168
+ });
185
169
  }
186
170
  }