jazz-tools 0.7.0-alpha.8 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) 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 +304 -0
  7. package/README.md +10 -2
  8. package/dist/coValues/account.js +59 -41
  9. package/dist/coValues/account.js.map +1 -1
  10. package/dist/coValues/coList.js +49 -46
  11. package/dist/coValues/coList.js.map +1 -1
  12. package/dist/coValues/coMap.js +143 -44
  13. package/dist/coValues/coMap.js.map +1 -1
  14. package/dist/coValues/coStream.js +144 -35
  15. package/dist/coValues/coStream.js.map +1 -1
  16. package/dist/coValues/deepLoading.js +60 -0
  17. package/dist/coValues/deepLoading.js.map +1 -0
  18. package/dist/coValues/extensions/imageDef.js +10 -7
  19. package/dist/coValues/extensions/imageDef.js.map +1 -1
  20. package/dist/coValues/group.js +49 -13
  21. package/dist/coValues/group.js.map +1 -1
  22. package/dist/coValues/interfaces.js +70 -31
  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 +58 -18
  27. package/dist/implementation/refs.js.map +1 -1
  28. package/dist/implementation/schema.js +58 -0
  29. package/dist/implementation/schema.js.map +1 -0
  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 +3 -5
  35. package/dist/index.js.map +1 -1
  36. package/dist/internal.js +5 -2
  37. package/dist/internal.js.map +1 -1
  38. package/dist/tests/coList.test.js +51 -48
  39. package/dist/tests/coList.test.js.map +1 -1
  40. package/dist/tests/coMap.test.js +131 -73
  41. package/dist/tests/coMap.test.js.map +1 -1
  42. package/dist/tests/coStream.test.js +56 -41
  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 +113 -125
  50. package/src/coValues/coList.ts +87 -103
  51. package/src/coValues/coMap.ts +200 -147
  52. package/src/coValues/coStream.ts +264 -80
  53. package/src/coValues/deepLoading.ts +229 -0
  54. package/src/coValues/extensions/imageDef.ts +17 -13
  55. package/src/coValues/group.ts +92 -58
  56. package/src/coValues/interfaces.ts +215 -115
  57. package/src/implementation/devtoolsFormatters.ts +110 -0
  58. package/src/implementation/inspect.ts +1 -1
  59. package/src/implementation/refs.ts +80 -28
  60. package/src/implementation/schema.ts +138 -0
  61. package/src/implementation/subscriptionScope.ts +48 -12
  62. package/src/implementation/symbols.ts +11 -0
  63. package/src/index.ts +12 -8
  64. package/src/internal.ts +7 -3
  65. package/src/tests/coList.test.ts +77 -62
  66. package/src/tests/coMap.test.ts +201 -113
  67. package/src/tests/coStream.test.ts +113 -84
  68. package/src/tests/deepLoading.test.ts +301 -0
  69. package/src/tests/groupsAndAccounts.test.ts +91 -0
  70. package/dist/implementation/encoding.js +0 -26
  71. package/dist/implementation/encoding.js.map +0 -1
  72. package/src/implementation/encoding.ts +0 -105
@@ -2,6 +2,7 @@ import { LocalNode } from "cojson";
2
2
  import type {
3
3
  AgentSecret,
4
4
  CoID,
5
+ CryptoProvider,
5
6
  InviteSecret,
6
7
  Peer,
7
8
  RawAccount,
@@ -15,116 +16,98 @@ import type {
15
16
  CoMap,
16
17
  CoValue,
17
18
  CoValueClass,
18
- Encoding,
19
- Group,
19
+ Schema,
20
20
  ID,
21
21
  RefEncoded,
22
- SubclassedConstructor,
23
- UnavailableError,
22
+ ClassOf,
23
+ RefIfCoValue,
24
24
  } from "../internal.js";
25
25
  import {
26
+ Group,
26
27
  CoValueBase,
28
+ MembersSym,
27
29
  Profile,
28
30
  Ref,
29
31
  SchemaInit,
30
32
  inspect,
31
33
  subscriptionsScopes,
32
34
  } from "../internal.js";
33
- import type { Stream } from "effect/Stream";
34
35
 
35
- export class Account<
36
- Def extends { profile: Profile | null; root: CoMap | null } = {
37
- profile: Profile | null;
38
- root: CoMap | null;
39
- },
40
- >
36
+ /** @category Identity & Permissions */
37
+ export class Account
41
38
  extends CoValueBase
42
39
  implements CoValue<"Account", RawAccount | RawControlledAccount>
43
40
  {
44
- id!: ID<this>;
45
- _type!: "Account";
46
- _raw!: RawAccount | RawControlledAccount;
41
+ declare id: ID<this>;
42
+ declare _type: "Account";
43
+ declare _raw: RawAccount | RawControlledAccount;
47
44
 
48
- static _encoding: any;
49
- get _encoding(): {
50
- profile: Encoding;
51
- root: Encoding;
45
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
46
+ static _schema: any;
47
+ get _schema(): {
48
+ profile: Schema;
49
+ root: Schema;
52
50
  } {
53
- return (this.constructor as typeof Account)._encoding;
51
+ return (this.constructor as typeof Account)._schema;
54
52
  }
55
53
  static {
56
- this._encoding = {
57
- profile: { ref: () => Profile },
58
- root: { json: true },
54
+ this._schema = {
55
+ profile: { ref: () => Profile, optional: false } satisfies Schema,
56
+ root: "json" satisfies Schema,
57
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
59
58
  } as any;
60
59
  }
61
60
 
62
61
  get _owner(): Account {
63
62
  return this as Account;
64
63
  }
65
- get _loadedAs(): Account & Me {
66
- return this.isMe
67
- ? (this as Account & Me)
68
- : Account.fromNode(this._raw.core.node);
64
+ get _loadedAs(): Account {
65
+ return this.isMe ? this : Account.fromNode(this._raw.core.node);
69
66
  }
70
67
 
71
- profile!: NonNullable<Def["profile"]> | null;
72
- root!: NonNullable<Def["root"]> | null;
68
+ declare profile: Profile | null;
69
+ declare root: CoMap | null;
73
70
 
74
- get _refs(): {
75
- profile: NonNullable<Def["profile"]> extends Profile
76
- ? Ref<NonNullable<Def["profile"]>> | null
77
- : null;
78
- root: NonNullable<Def["root"]> extends CoMap
79
- ? Ref<NonNullable<Def["root"]>> | null
80
- : null;
81
- } {
71
+ get _refs() {
82
72
  const profileID = this._raw.get("profile") as unknown as
83
- | ID<NonNullable<Def["profile"]>>
73
+ | ID<NonNullable<this["profile"]>>
84
74
  | undefined;
85
75
  const rootID = this._raw.get("root") as unknown as
86
- | ID<NonNullable<Def["root"]>>
76
+ | ID<NonNullable<this["root"]>>
87
77
  | undefined;
78
+
88
79
  return {
89
80
  profile:
90
81
  profileID &&
91
82
  (new Ref(
92
83
  profileID,
93
84
  this._loadedAs,
94
- this._encoding.profile as RefEncoded<
95
- NonNullable<Def["profile"]> & CoValue
96
- >
97
- ) as any),
85
+ this._schema.profile as RefEncoded<
86
+ NonNullable<this["profile"]> & CoValue
87
+ >,
88
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
89
+ ) as any as RefIfCoValue<this["profile"]>),
98
90
  root:
99
91
  rootID &&
100
92
  (new Ref(
101
93
  rootID,
102
94
  this._loadedAs,
103
- this._encoding.root as RefEncoded<
104
- NonNullable<Def["root"]> & CoValue
105
- >
106
- ) as any),
95
+ this._schema.root as RefEncoded<
96
+ NonNullable<this["root"]> & CoValue
97
+ >,
98
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
99
+ ) as any as RefIfCoValue<this["root"]>),
107
100
  };
108
101
  }
109
102
 
110
103
  isMe: boolean;
111
104
  sessionID: SessionID | undefined;
112
105
 
113
- constructor(init: undefined, options: { owner: Group | Account });
114
- constructor(
115
- init: undefined,
116
- options: { fromRaw: RawAccount | RawControlledAccount }
117
- );
118
- constructor(
119
- init: undefined,
120
- options:
121
- | { fromRaw: RawAccount | RawControlledAccount }
122
- | { owner: Group | Account }
123
- ) {
106
+ constructor(options: { fromRaw: RawAccount | RawControlledAccount }) {
124
107
  super();
125
108
  if (!("fromRaw" in options)) {
126
109
  throw new Error(
127
- "Can only construct account from raw or with .create()"
110
+ "Can only construct account from raw or with .create()",
128
111
  );
129
112
  }
130
113
  this.isMe = options.fromRaw.id == options.fromRaw.core.node.account.id;
@@ -135,16 +118,16 @@ export class Account<
135
118
  enumerable: false,
136
119
  },
137
120
  _raw: { value: options.fromRaw, enumerable: false },
121
+ _type: { value: "Account", enumerable: false },
138
122
  });
139
123
 
140
124
  if (this.isMe) {
141
- (this as Account & Me).sessionID =
142
- options.fromRaw.core.node.currentSessionID;
125
+ this.sessionID = options.fromRaw.core.node.currentSessionID;
143
126
  }
144
127
 
145
128
  return new Proxy(
146
129
  this,
147
- AccountAndGroupProxyHandler as ProxyHandler<this>
130
+ AccountAndGroupProxyHandler as ProxyHandler<this>,
148
131
  );
149
132
  }
150
133
 
@@ -154,88 +137,82 @@ export class Account<
154
137
  }
155
138
  }
156
139
 
157
- acceptInvite:
158
- | (<V extends CoValue>(
159
- valueID: ID<V>,
160
- inviteSecret: InviteSecret,
161
- coValueClass: CoValueClass<V>
162
- ) => Promise<V | undefined>)
163
- | undefined = (async <V extends CoValue>(
140
+ async acceptInvite<V extends CoValue>(
164
141
  valueID: ID<V>,
165
142
  inviteSecret: InviteSecret,
166
- coValueClass: CoValueClass<V>
167
- ) => {
143
+ coValueClass: CoValueClass<V>,
144
+ ) {
168
145
  if (!this.isMe) {
169
146
  throw new Error("Only a controlled account can accept invites");
170
147
  }
171
148
 
172
149
  await (this._raw as RawControlledAccount).acceptInvite(
173
150
  valueID as unknown as CoID<RawCoValue>,
174
- inviteSecret
151
+ inviteSecret,
175
152
  );
176
153
 
177
- return coValueClass.load(valueID, {
178
- as: this as Account & Me,
179
- });
180
- }) as any;
154
+ return coValueClass.load(valueID, this as Account, []);
155
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
156
+ }
181
157
 
182
158
  static async create<A extends Account>(
183
- this: SubclassedConstructor<A> & typeof Account,
159
+ this: ClassOf<A> & typeof Account,
184
160
  options: {
185
- name: string;
161
+ creationProps: { name: string };
186
162
  initialAgentSecret?: AgentSecret;
187
163
  peersToLoadFrom?: Peer[];
188
- }
189
- ): Promise<A & Me> {
164
+ crypto: CryptoProvider;
165
+ },
166
+ ): Promise<A> {
190
167
  const { node } = await LocalNode.withNewlyCreatedAccount({
191
168
  ...options,
192
- migration: async (rawAccount) => {
193
- const account = new this(undefined, {
169
+ migration: async (rawAccount, _node, creationProps) => {
170
+ const account = new this({
194
171
  fromRaw: rawAccount,
195
- }) as A & Me;
172
+ }) as A;
196
173
 
197
- await account.migrate?.();
174
+ await account.migrate?.(creationProps);
198
175
  },
199
176
  });
200
177
 
201
- return this.fromNode(node) as A & Me;
178
+ return this.fromNode(node) as A;
202
179
  }
203
180
 
204
181
  static async become<A extends Account>(
205
- this: SubclassedConstructor<A> & typeof Account,
182
+ this: ClassOf<A> & typeof Account,
206
183
  options: {
207
184
  accountID: ID<Account>;
208
185
  accountSecret: AgentSecret;
209
- sessionID: SessionID;
186
+ sessionID?: SessionID;
210
187
  peersToLoadFrom: Peer[];
211
- }
212
- ): Promise<A & Me> {
188
+ crypto: CryptoProvider;
189
+ },
190
+ ): Promise<A> {
213
191
  const node = await LocalNode.withLoadedAccount({
214
192
  accountID: options.accountID as unknown as CoID<RawAccount>,
215
193
  accountSecret: options.accountSecret,
216
194
  sessionID: options.sessionID,
217
195
  peersToLoadFrom: options.peersToLoadFrom,
218
- migration: async (rawAccount) => {
219
- const account = new this(undefined, {
196
+ crypto: options.crypto,
197
+ migration: async (rawAccount, _node, creationProps) => {
198
+ const account = new this({
220
199
  fromRaw: rawAccount,
221
- }) as A & Me;
200
+ }) as A;
222
201
 
223
- await account.migrate?.();
202
+ await account.migrate?.(creationProps);
224
203
  },
225
204
  });
226
205
 
227
- return this.fromNode(node) as A & Me;
206
+ return this.fromNode(node) as A;
228
207
  }
229
208
 
230
- static fromNode<A extends Account>(
231
- this: SubclassedConstructor<A>,
232
- node: LocalNode
233
- ): A & Me {
234
- return new this(undefined, {
209
+ static fromNode<A extends Account>(this: ClassOf<A>, node: LocalNode): A {
210
+ return new this({
235
211
  fromRaw: node.account as RawControlledAccount,
236
- }) as A & Me;
212
+ }) as A;
237
213
  }
238
214
 
215
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
239
216
  toJSON(): object | any[] {
240
217
  return {
241
218
  id: this.id,
@@ -247,45 +224,63 @@ export class Account<
247
224
  return this.toJSON();
248
225
  }
249
226
 
250
- migrate: (() => void | Promise<void>) | undefined;
227
+ migrate(creationProps?: { name: string }): void | Promise<void> {
228
+ if (creationProps) {
229
+ const profileGroup = Group.create({ owner: this });
230
+ profileGroup.addMember("everyone", "reader");
231
+ this.profile = Profile.create(
232
+ { name: creationProps.name },
233
+ { owner: profileGroup },
234
+ );
235
+ }
236
+ }
251
237
  }
252
238
 
253
239
  export const AccountAndGroupProxyHandler: ProxyHandler<Account | Group> = {
254
240
  get(target, key, receiver) {
255
241
  if (key === "profile") {
256
242
  const ref = target._refs.profile;
257
- return ref ? ref.accessFrom(receiver) : (undefined as any);
243
+ return ref
244
+ ? ref.accessFrom(receiver, "profile")
245
+ : // eslint-disable-next-line @typescript-eslint/no-explicit-any
246
+ (undefined as any);
258
247
  } else if (key === "root") {
259
248
  const ref = target._refs.root;
260
- return ref ? ref.accessFrom(receiver) : (undefined as any);
249
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
250
+ return ref ? ref.accessFrom(receiver, "root") : (undefined as any);
261
251
  } else {
262
252
  return Reflect.get(target, key, receiver);
263
253
  }
264
254
  },
265
255
  set(target, key, value, receiver) {
266
256
  if (
267
- (key === "profile" || key === "root") &&
257
+ (key === "profile" || key === "root" || key === MembersSym) &&
268
258
  typeof value === "object" &&
269
259
  SchemaInit in value
270
260
  ) {
271
- (target.constructor as typeof CoMap)._encoding ||= {};
272
- (target.constructor as typeof CoMap)._encoding[key] =
261
+ (target.constructor as typeof CoMap)._schema ||= {};
262
+ (target.constructor as typeof CoMap)._schema[key] =
273
263
  value[SchemaInit];
274
264
  return true;
275
265
  } else if (key === "profile") {
276
266
  if (value) {
277
267
  target._raw.set(
278
268
  "profile",
279
- value.id as unknown as CoID<RawCoMap>
269
+ value.id as unknown as CoID<RawCoMap>,
270
+ "trusting",
280
271
  );
281
272
  }
282
- subscriptionsScopes.get(receiver)?.onRefAccessedOrSet(value.id);
273
+ subscriptionsScopes
274
+ .get(receiver)
275
+ ?.onRefAccessedOrSet(target.id, value.id);
283
276
  return true;
284
277
  } else if (key === "root") {
285
278
  if (value) {
286
279
  target._raw.set("root", value.id as unknown as CoID<RawCoMap>);
287
280
  }
288
- subscriptionsScopes.get(receiver)?.onRefAccessedOrSet(value.id);
281
+ subscriptionsScopes
282
+ .get(receiver)
283
+ ?.onRefAccessedOrSet(target.id, value.id);
289
284
  return true;
290
285
  } else {
291
286
  return Reflect.set(target, key, value, receiver);
@@ -293,12 +288,12 @@ export const AccountAndGroupProxyHandler: ProxyHandler<Account | Group> = {
293
288
  },
294
289
  defineProperty(target, key, descriptor) {
295
290
  if (
296
- (key === "profile" || key === "root") &&
291
+ (key === "profile" || key === "root" || key === MembersSym) &&
297
292
  typeof descriptor.value === "object" &&
298
293
  SchemaInit in descriptor.value
299
294
  ) {
300
- (target.constructor as typeof CoMap)._encoding ||= {};
301
- (target.constructor as typeof CoMap)._encoding[key] =
295
+ (target.constructor as typeof CoMap)._schema ||= {};
296
+ (target.constructor as typeof CoMap)._schema[key] =
302
297
  descriptor.value[SchemaInit];
303
298
  return true;
304
299
  } else {
@@ -307,21 +302,14 @@ export const AccountAndGroupProxyHandler: ProxyHandler<Account | Group> = {
307
302
  },
308
303
  };
309
304
 
310
- export interface Me {
311
- id: ID<any>;
305
+ /** @category Identity & Permissions */
306
+ export class AccountCtx extends Context.Tag("Account")<AccountCtx, Account>() {}
307
+
308
+ /** @category Identity & Permissions */
309
+ export function isControlledAccount(account: Account): account is Account & {
312
310
  isMe: true;
313
- _raw: RawControlledAccount;
314
311
  sessionID: SessionID;
315
- subscribe(listener: (update: this & Me) => void): () => void;
316
- subscribeEf(): Stream<this & Me, UnavailableError, never>;
317
- acceptInvite: (...args: any[]) => any;
318
- }
319
-
320
- export class AccountCtx extends Context.Tag("Account")<
321
- AccountCtx,
322
- Account & Me
323
- >() {}
324
-
325
- export function isControlledAccount(account: Account): account is Account & Me {
312
+ _raw: RawControlledAccount;
313
+ } {
326
314
  return account.isMe;
327
315
  }